Passed
Branch master (580104)
by Omar
03:52 queued 32s
created

DiffFinder::buildLocalLocation()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 5
ccs 3
cts 3
cp 1
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
crap 1
1
<?php
2
3
namespace TemplesOfCode\CodeSanity;
4
5
use Doctrine\Common\Collections\ArrayCollection;
6
use TemplesOfCode\CodeSanity\Location\LocalLocation;
7
use TemplesOfCode\CodeSanity\Location\RemoteLocation;
8
9
/**
10
 * Class DiffFinder
11
 * @package TemplesOfCode\CodeSanity
12
 */
13
class DiffFinder
14
{
15
    /**
16
     * @var string
17
     */
18
    protected static $localLocationPattern=<<<PATTERN
19
/(\/[A-Za-z0-9_\-\.]+)*\/?/
20
PATTERN;
21
22
    /**
23
     * @var array
24
     */
25
    protected static $remoteLocationPatterns = array(
26
        'user' => '[a-zA-Z][a-zA-Z0-9_]+',
27
        'host' => '([a-zA-Z][a-zA-Z0-9\-]*\.)*[a-zA-Z][a-zA-Z0-9\-]*',
28
        'path' => '(\/[A-Za-z0-9_\-\.]+)*\/?'
29
    );
30
31
    /**
32
     * @var null
33
     */
34
    protected static $remoteLocationPattern = null;
35
36
    /**
37
     * @var Location
38
     */
39
    protected $sourceOfTruth;
40
41
    /**
42
     * @var ArrayCollection
43
     */
44
    protected $targetLocations;
45
46
    /**
47
     * DiffFinder constructor.
48
     *
49
     * @param string $sourceLocation
50
     * @param ArrayCollection $targets
51
     */
52 12
    public function __construct($sourceLocation = null, ArrayCollection $targets = null)
53
    {
54 12
        self::$remoteLocationPattern = sprintf(
0 ignored issues
show
Documentation Bug introduced by
It seems like sprintf('/^%s\\@%s\\:%s/...cationPatterns['path']) of type string is incompatible with the declared type null of property $remoteLocationPattern.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
55 12
            '/^%s\@%s\:%s/',
56 12
            self::$remoteLocationPatterns['user'],
57 12
            self::$remoteLocationPatterns['host'],
58 12
            self::$remoteLocationPatterns['path']
59
60 12
        );
61
62 12
        $this->targetLocations = new ArrayCollection();
63 12
        if (!is_null($sourceLocation)) {
64 1
            $this->resolveSourceLocation($sourceLocation);
65 1
        }
66
67 12
        if (!is_null($targets) && !$targets->isEmpty()) {
68 1
            $this->resolveTargetLocations($targets);
69 1
        }
70 12
    }
71
72
    /**
73
     * @return ArrayCollection
74
     */
75 3
    public function getTargetLocations()
76
    {
77 3
        return $this->targetLocations;
78
    }
79
80
    /**
81
     * @param Location $targetLocation
82
     * @return $this
83
     */
84 10
    public function addTargetLocation(Location $targetLocation)
85
    {
86
        /**
87
         * @var bool $targetLocationPresents
88
         */
89 10
        $targetLocationPresent = $this
90
            ->targetLocations
91 10
            ->contains($targetLocation)
92 10
        ;
93
94 10
        if (!$targetLocationPresent) {
95 10
            $this->targetLocations->add($targetLocation);
96 10
        }
97
98 10
        return $this;
99
    }
100
101
    /**
102
     * @param Location $targetLocation
103
     * @return $this
104
     */
105 2
    public function removeTargetLocation(Location $targetLocation)
106
    {
107
        /**
108
         * @var bool $targetLocationPresent
109
         */
110 2
        $targetLocationPresent = $this
111
            ->targetLocations
112 2
            ->contains($targetLocation)
113 2
        ;
114
115 2
        if ($targetLocationPresent) {
116 2
            $this->targetLocations->removeElement($targetLocation);
117 2
        }
118
119 2
        return $this;
120
    }
121
122
    /**
123
     * @return Location
124
     */
125 2
    public function getSourceOfTruth()
126
    {
127 2
        return $this->sourceOfTruth;
128
    }
129
130
    /**
131
     * @param Location $sourceOfTruth
132
     * @return DiffFinder
133
     */
134 10
    public function setSourceOfTruth(Location $sourceOfTruth)
135
    {
136 10
        $this->sourceOfTruth = $sourceOfTruth;
137 10
        return $this;
138
    }
139
140
    /**
141
     * @return bool
142
     */
143 1
    protected function validateResources()
144
    {
145
        /**
146
         * @var bool $validSourceOfTruth
147
         */
148 1
        $validSourceOfTruth = $this->sourceOfTruth->isValid();
149 1
        if (!$validSourceOfTruth) {
150 1
            return false;
151
        }
152
153 1
        if ($this->targetLocations->isEmpty()) {
154 1
            return false;
155
        }
156
157
        /**
158
         * @var bool $validTargetLocations
159
         */
160 1
        $validTargetLocations = true;
161 1
        foreach ($this->targetLocations->toArray() as $location) {
162
            /**
163
             * @var Location $location
164
             */
165
166
            /**
167
             * @var bool $validTargetLocations
168
             */
169 1
            $validTargetLocations = $validTargetLocations && $location->isValid();
170 1
            if (!$validTargetLocations) {
171 1
                break;
172
            }
173 1
        }
174
175 1
        return $validTargetLocations;
176
    }
177
178
    /**
179
     * @return ArrayCollection
180
     * @throws \Exception
181
     */
182 8
    public function find()
183
    {
184
        /**
185
         * @var bool $resourcesValidated
186
         */
187 8
        $resourcesValidated = $this->validateResources();
188 8
        if (!$resourcesValidated) {
189 1
            throw new \Exception("Resources needed to find differences not complete");
190
        }
191
192 7
        $targetRosters = new ArrayCollection();
193 7
        foreach ($this->targetLocations as $location) {
194
            /**
195
             * @var Location $location
196
             */
197
198
            /**
199
             * @var Roster $targetRoster
200
             */
201 7
            $targetRoster = $location->buildRoster();
202 7
            $targetRosters->add($targetRoster);
203 7
        }
204
205
        /**
206
         * @var Roster $sotRoster
207
         */
208 7
        $sotRoster = $this->sourceOfTruth->buildRoster();
209
210 7
        $differences = $this->compareAllRosters($sotRoster, $targetRosters);
211 7
        return $differences;
212
    }
213
214
    /**
215
     * @param Roster $sotRoster
216
     * @param ArrayCollection $targetRosters
217
     * @return ArrayCollection
218
     */
219 7
    private function compareAllRosters(Roster $sotRoster, ArrayCollection $targetRosters)
220
    {
221 7
        $differences = new ArrayCollection();
222
223 7
        foreach ($targetRosters as $roster) {
224
            /**
225
             * @var ArrayCollection $differenceSet
226
             */
227 7
            $differenceSet = $this->compareRosters($sotRoster, $roster);
228 7
            if ($differenceSet->count()) {
229 6
                $differences->add($differenceSet);
230 6
            }
231 7
        }
232
233 7
        return $differences;
234
    }
235
236
    /**
237
     * @param Roster $sotRoster
238
     * @param Roster $targetRoster
239
     * @return ArrayCollection
240
     */
241 7
    private function compareRosters(Roster $sotRoster, Roster $targetRoster)
242
    {
243 7
        $differenceSet = new ArrayCollection();
244
245 7
        $processedItems = new ArrayCollection();
246
247 7
        foreach ($sotRoster->getRosterItems()->toArray() as  $fileName => $rosterItem) {
248
249 7
            $fileName = (string)$fileName;
250
251
            /**
252
             * @var RosterItem $rosterItem
253
             */
254
255 7
            if (!$targetRoster->getRosterItems()->containsKey($fileName)) {
256
                /**
257
                 * Target roster missing the source of truth roster item.
258
                 */
259 4
                $difference = new DiffItem();
260 4
                $difference->setSotRosterItem($rosterItem);
261 4
                $differenceSet->set($fileName, $difference);
262 4
                continue;
263
            }
264
265
            /**
266
             * @var RosterItem $targetItem
267
             */
268 7
            $targetItem = $targetRoster->getRosterItems()->get($fileName);
269
270
            /**
271
             * @var string $targetFilename
272
             */
273 7
            $targetFilename = $targetItem->getRelativeFileName();
274
275 7
            $processedItems->add($targetFilename);
276
277 7
            if ($rosterItem->getHash() == $targetItem->getHash()) {
278 7
                continue;
279
            }
280
281
            /**
282
             * Items differ
283
             */
284 4
            $difference = new DiffItem();
285 4
            $difference->setSotRosterItem($rosterItem);
286 4
            $difference->setTargetRosterItem($targetItem);
287 4
            $differenceSet->set($fileName, $difference);
288 7
        }
289
290
        /**
291
         * Find the items missing from source of truth.
292
         */
293 7
        foreach ($targetRoster->getRosterItems()->toArray() as $fileName => $rosterItem) {
294
295 7
            $fileName = (string)$fileName;
296 7
            if ($processedItems->contains($fileName)) {
297
                /**
298
                 * Already dealt with in previous loop
299
                 */
300 7
                continue;
301
            }
302
303
            /**
304
             * Source of truth roster missing the target roster item.
305
             */
306 4
            $difference = new DiffItem();
307 4
            $difference->setTargetRosterItem($rosterItem);
308 4
            $differenceSet->set($fileName, $difference);
309 7
        }
310
311 7
        return $differenceSet;
312
    }
313
314
315
    /**
316
     * @param string|null $source
317
     * @return bool
318
     */
319 1
    protected function resolveSourceLocation($source = null)
320
    {
321
        /**
322
         * @var Location $location
323
         */
324 1
        $location = $this->resolveLocation($source);
325
326 1
        if (!is_null($location)) {
327 1
            $this->setSourceOfTruth($location);
328 1
        }
329
330 1
        return true;
331
    }
332
333
    /**
334
     * @param ArrayCollection|null $targets
335
     * @return bool
336
     */
337 1
    protected function resolveTargetLocations(ArrayCollection  $targets = null)
338
    {
339 1
        foreach ($targets as $target) {
0 ignored issues
show
Bug introduced by
The expression $targets of type null|object<Doctrine\Com...ctions\ArrayCollection> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
340
            /**
341
             * @var string $target
342
             * @var Location $location
343
             */
344 1
            $location = $this->resolveLocation($target);
345 1
            $this->addTargetLocation($location);
346 1
        }
347
348 1
        return true;
349
    }
350
351
    /**
352
     * @param string $location
353
     * @return  Location|null
354
     */
355 1
    protected function resolveLocation($location)
356
    {
357 1
        $resolvedLocation = null;
358
359 1
        $matches = array();
360 1
        if (preg_match(self::$remoteLocationPattern, $location, $matches)) {
361
362
            /**
363
             * @var string $username
364
             */
365 1
            $username = $matches[0];
366
367
            /**
368
             * @var string $host
369
             */
370 1
            $host = $matches[1];
371
372
            /**
373
             * @var string $directory
374
             */
375 1
            $directory = $matches[2];
376
377
            /**
378
             * @var RemoteLocation $resolvedLocation
379
             */
380 1
            $resolvedLocation = $this->buildRemoteLocation($username, $host, $directory);
381 1
        }
382 1
        else if (preg_match(self::$localLocationPattern, $location)) {
383
            /**
384
             * @var LocalLocation $resolvedLocation
385
             */
386 1
            $resolvedLocation = $this->buildLocalLocation($location);
387 1
        }
388
389 1
        return $resolvedLocation;
390
    }
391
392
    /**
393
     *
394
     * todo: can this be static?
395
     *
396
     * @param string $username
397
     * @param string $host
398
     * @param string $directory
399
     * @return RemoteLocation
400
     */
401 1
    protected function buildRemoteLocation($username, $host, $directory)
402
    {
403 1
        $location = new RemoteLocation($directory);
404
        $options = array(
405 1
            'executable' => 'ssh',
406 1
            'host' => $host,
407 1
            'port' => 22,
408 1
            'username' => $username,
409 1
        );
410 1
        $connection = new RemoteConnection($options);
411 1
        $location->setRemoteConnection($connection);
412 1
        return $location;
413
414
    }
415
416
    /**
417
     * todo: can this be static?
418
     *
419
     * @param string $location
420
     * @return LocalLocation
421
     */
422 1
    protected function buildLocalLocation($location)
423
    {
424 1
        $location = new LocalLocation($location);
425 1
        return $location;
426
    }
427
}
428