Build   B
last analyzed

Complexity

Total Complexity 38

Size/Duplication

Total Lines 289
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 7

Test Coverage

Coverage 24.55%

Importance

Changes 12
Bugs 3 Features 1
Metric Value
wmc 38
c 12
b 3
f 1
lcom 3
cbo 7
dl 0
loc 289
ccs 27
cts 110
cp 0.2455
rs 8.3999

15 Methods

Rating   Name   Duplication   Size   Complexity  
A getCommitLink() 0 4 1
A getBranchLink() 0 4 1
A getFileLinkTemplate() 0 4 1
A sendStatusPostback() 0 4 1
A getProjectTitle() 0 6 2
A storeMeta() 0 5 1
A isSuccessful() 0 4 1
C handleConfig() 0 33 7
C getZeroConfigPlugins() 0 45 8
A getExtra() 0 14 3
A getCommitMessage() 0 6 1
A reportError() 0 23 1
A getBuildPath() 0 13 3
A removeBuildDirectory() 0 10 4
A getDuration() 0 16 3
1
<?php
2
/**
3
 * PHPCI - Continuous Integration for PHP.
4
 *
5
 * @copyright    Copyright 2014, Block 8 Limited.
6
 * @license      https://github.com/Block8/PHPCI/blob/master/LICENSE.md
7
 *
8
 * @link         https://www.phptesting.org/
9
 */
10
11
namespace PHPCI\Model;
12
13
use b8\Store\Factory;
14
use PHPCI\Model\Base\BuildBase;
15
use PHPCI\Builder;
16
use Symfony\Component\Yaml\Parser as YamlParser;
17
18
/**
19
 * Build Model.
20
 *
21
 * @uses         PHPCI\Model\Base\BuildBase
22
 *
23
 * @author       Dan Cryer <[email protected]>
24
 */
25
class Build extends BuildBase
26
{
27
    const STATUS_NEW = 0;
28
    const STATUS_RUNNING = 1;
29
    const STATUS_SUCCESS = 2;
30
    const STATUS_FAILED = 3;
31
32
    public $currentBuildPath;
33
34
    /**
35
     * Get link to commit from another source (i.e. Github).
36
     */
37 13
    public function getCommitLink()
38
    {
39 13
        return '#';
40
    }
41
42
    /**
43
     * Get link to branch from another source (i.e. Github).
44
     */
45 1
    public function getBranchLink()
46
    {
47 1
        return '#';
48
    }
49
50
    /**
51
     * Return a template to use to generate a link to a specific file.
52
     *
53
     * @return null
54
     */
55 1
    public function getFileLinkTemplate()
56
    {
57 1
        return;
58
    }
59
60
    /**
61
     * Send status updates to any relevant third parties (i.e. Github).
62
     */
63
    public function sendStatusPostback()
64
    {
65
        return;
66
    }
67
68
    /**
69
     * @return string
70
     */
71
    public function getProjectTitle()
72
    {
73
        $project = $this->getProject();
74
75
        return $project ? $project->getTitle() : '';
76
    }
77
78
    /**
79
     * Store build metadata.
80
     */
81
    public function storeMeta($key, $value)
82
    {
83
        $value = json_encode($value);
84
        Factory::getStore('Build')->setMeta($this->getProjectId(), $this->getId(), $key, $value);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class b8\Store as the method setMeta() does only exist in the following sub-classes of b8\Store: PHPCI\Store\BuildStore. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
85
    }
86
87
    /**
88
     * Is this build successful?
89
     */
90 13
    public function isSuccessful()
91
    {
92 13
        return $this->getStatus() === self::STATUS_SUCCESS;
93
    }
94
95
    /**
96
     * @param Builder $builder
97
     * @param string  $buildPath
98
     *
99
     * @return bool
100
     */
101
    protected function handleConfig(Builder $builder, $buildPath)
102
    {
103
        $build_config = null;
104
105
        // Try getting the project build config from the database:
106
        if (empty($build_config)) {
107
            $build_config = $this->getProject()->getBuildConfig();
108
        }
109
110
        // Try .phpci.yml
111
        if (is_file($buildPath.'/.phpci.yml')) {
112
            $build_config = file_get_contents($buildPath.'/.phpci.yml');
113
        }
114
115
        // Try phpci.yml first:
116
        if (empty($build_config) && is_file($buildPath.'/phpci.yml')) {
117
            $build_config = file_get_contents($buildPath.'/phpci.yml');
118
        }
119
120
        // Fall back to zero config plugins:
121
        if (empty($build_config)) {
122
            $build_config = $this->getZeroConfigPlugins($builder);
123
        }
124
125
        if (is_string($build_config)) {
126
            $yamlParser = new YamlParser();
127
            $build_config = $yamlParser->parse($build_config);
128
        }
129
130
        $builder->setConfigArray($build_config);
131
132
        return true;
133
    }
134
135
    /**
136
     * Get an array of plugins to run if there's no phpci.yml file.
137
     *
138
     * @param Builder $builder
139
     *
140
     * @return array
141
     */
142
    protected function getZeroConfigPlugins(Builder $builder)
143
    {
144
        $pluginDir = PHPCI_DIR.'PHPCI/Plugin/';
145
        $dir = new \DirectoryIterator($pluginDir);
146
147
        $config = array(
148
            'build_settings' => array(
149
                'ignore' => array(
150
                    'vendor',
151
                ),
152
            ),
153
        );
154
155
        foreach ($dir as $item) {
156
            if ($item->isDot()) {
157
                continue;
158
            }
159
160
            if (!$item->isFile()) {
161
                continue;
162
            }
163
164
            if ($item->getExtension() != 'php') {
165
                continue;
166
            }
167
168
            $className = '\PHPCI\Plugin\\'.$item->getBasename('.php');
169
170
            $reflectedPlugin = new \ReflectionClass($className);
171
172
            if (!$reflectedPlugin->implementsInterface('\PHPCI\ZeroConfigPlugin')) {
173
                continue;
174
            }
175
176
            foreach (array('setup', 'test', 'complete', 'success', 'failure') as $stage) {
177
                if ($className::canExecute($stage, $builder, $this)) {
178
                    $config[$stage][$className] = array(
179
                        'zero_config' => true,
180
                    );
181
                }
182
            }
183
        }
184
185
        return $config;
186
    }
187
188
    /**
189
     * Return a value from the build's "extra" JSON array.
190
     *
191
     * @param null $key
192
     *
193
     * @return mixed|null|string
194
     */
195 4
    public function getExtra($key = null)
196
    {
197 4
        $data = json_decode($this->data['extra'], true);
198
199 4
        if (is_null($key)) {
200 2
            $rtn = $data;
201 4
        } elseif (isset($data[$key])) {
202 3
            $rtn = $data[$key];
203 3
        } else {
204 1
            $rtn = null;
205
        }
206
207 4
        return $rtn;
208
    }
209
210
    /**
211
     * Returns the commit message for this build.
212
     *
213
     * @return string
214
     */
215 15
    public function getCommitMessage()
216
    {
217 15
        $rtn = htmlspecialchars($this->data['commit_message']);
218
219 15
        return $rtn;
220
    }
221
222
    /**
223
     * Allows specific build types (e.g. Github) to report violations back to their respective services.
224
     *
225
     * @param Builder $builder
226
     * @param $plugin
227
     * @param $message
228
     * @param int  $severity
229
     * @param null $file
230
     * @param null $lineStart
231
     * @param null $lineEnd
232
     *
233
     * @return BuildError
234
     */
235
    public function reportError(
236
        Builder $builder,
237
        $plugin,
238
        $message,
239
        $severity = BuildError::SEVERITY_NORMAL,
240
        $file = null,
241
        $lineStart = null,
242
        $lineEnd = null
243
    ) {
244
        unset($builder);
245
246
        $error = new BuildError();
247
        $error->setBuild($this);
248
        $error->setCreatedDate(new \DateTime());
249
        $error->setPlugin($plugin);
250
        $error->setMessage($message);
251
        $error->setSeverity($severity);
252
        $error->setFile($file);
253
        $error->setLineStart($lineStart);
254
        $error->setLineEnd($lineEnd);
255
256
        return Factory::getStore('BuildError')->save($error);
257
    }
258
259
    /**
260
     * Return the path to run this build into.
261
     *
262
     * @return string|null
263
     */
264 1
    public function getBuildPath()
265
    {
266 1
        if (!$this->getId()) {
267 1
            return;
268
        }
269
270
        if (empty($this->currentBuildPath)) {
271
            $buildDirectory = $this->getId().'_'.substr(md5(microtime(true)), 0, 5);
272
            $this->currentBuildPath = PHPCI_BUILD_ROOT_DIR.$buildDirectory.DIRECTORY_SEPARATOR;
273
        }
274
275
        return $this->currentBuildPath;
276
    }
277
278
    /**
279
     * Removes the build directory.
280
     */
281 1
    public function removeBuildDirectory()
282
    {
283 1
        $buildPath = $this->getBuildPath();
284
285 1
        if (!$buildPath || !is_dir($buildPath)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $buildPath of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
286 1
            return;
287
        }
288
289
        exec(sprintf(IS_WIN ? 'rmdir /S /Q "%s"' : 'rm -Rf "%s"', $buildPath));
290
    }
291
292
    /**
293
     * Get the number of seconds a build has been running for.
294
     *
295
     * @return int
296
     */
297
    public function getDuration()
298
    {
299
        $start = $this->getStarted();
300
301
        if (empty($start)) {
302
            return 0;
303
        }
304
305
        $end = $this->getFinished();
306
307
        if (empty($end)) {
308
            $end = new \DateTime();
309
        }
310
311
        return $end->getTimestamp() - $start->getTimestamp();
312
    }
313
}
314