Failed Conditions
Pull Request — master (#56)
by Bernhard
11:07
created

DiscoveryAssetManager::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 2
Metric Value
c 2
b 0
f 2
dl 0
loc 7
ccs 6
cts 6
cp 1
rs 9.4285
cc 1
eloc 5
nc 1
nop 3
crap 1
1
<?php
2
3
/*
4
 * This file is part of the puli/manager package.
5
 *
6
 * (c) Bernhard Schussek <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Puli\Manager\Asset;
13
14
use Puli\Discovery\Binding\ResourceBinding;
15
use Puli\Manager\Api\Asset\AssetManager;
16
use Puli\Manager\Api\Asset\AssetMapping;
17
use Puli\Manager\Api\Asset\DuplicateAssetMappingException;
18
use Puli\Manager\Api\Asset\NoSuchAssetMappingException;
19
use Puli\Manager\Api\Discovery\BindingDescriptor;
20
use Puli\Manager\Api\Discovery\DiscoveryManager;
21
use Puli\Manager\Api\Event\AddAssetMappingEvent;
22
use Puli\Manager\Api\Event\PuliEvents;
23
use Puli\Manager\Api\Event\RemoveAssetMappingEvent;
24
use Puli\Manager\Api\Server\NoSuchServerException;
25
use Puli\Manager\Api\Server\ServerCollection;
26
use Puli\UrlGenerator\DiscoveryUrlGenerator;
27
use Rhumsaa\Uuid\Uuid;
28
use RuntimeException;
29
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
30
use Webmozart\Expression\Expr;
31
use Webmozart\Expression\Expression;
32
33
/**
34
 * An asset manager that uses a {@link DiscoveryManager} as storage backend.
35
 *
36
 * @since  1.0
37
 *
38
 * @author Bernhard Schussek <[email protected]>
39
 */
40
class DiscoveryAssetManager implements AssetManager
41
{
42
    /**
43
     * @var DiscoveryManager
44
     */
45
    private $discoveryManager;
46
47
    /**
48
     * @var ServerCollection
49
     */
50
    private $servers;
51
52
    /**
53
     * @var BindingExpressionBuilder
54
     */
55
    private $exprBuilder;
56
57
    /**
58
     * @var EventDispatcherInterface
59
     */
60
    private $dispatcher;
61
62 36
    public function __construct(DiscoveryManager $discoveryManager, ServerCollection $servers, EventDispatcherInterface $dispatcher = null)
63
    {
64 36
        $this->discoveryManager = $discoveryManager;
65 36
        $this->servers = $servers;
66 36
        $this->exprBuilder = new BindingExpressionBuilder();
67 36
        $this->dispatcher = $dispatcher;
68 36
    }
69
70
    /**
71
     * {@inheritdoc}
72
     */
73
    public function addRootAssetMapping(AssetMapping $mapping, $flags = 0)
74
    {
75
        if (!$this->discoveryManager->hasTypeDescriptor(DiscoveryUrlGenerator::BINDING_TYPE)) {
76
            throw new RuntimeException(sprintf(
77
                'The binding type "%s" was not found. Please install the '.
78
                '"puli/url-generator" module with Composer:'."\n\n".
79
                '    $ composer require puli/url-generator',
80
                DiscoveryUrlGenerator::BINDING_TYPE
81
            ));
82
        }
83
84
        if (!($flags & self::IGNORE_SERVER_NOT_FOUND) && !$this->servers->contains($mapping->getServerName())) {
85
            throw NoSuchServerException::forServerName($mapping->getServerName());
86
        }
87
88
        if (!($flags & self::OVERRIDE) && $this->hasAssetMapping($mapping->getUuid())) {
89
            throw DuplicateAssetMappingException::forUuid($mapping->getUuid());
90
        }
91
92
        $binding = new ResourceBinding(
93
            // Match directories as well as all of their contents
94
            $mapping->getGlob().'{,/**/*}',
95
            DiscoveryUrlGenerator::BINDING_TYPE,
96
            array(
97
                DiscoveryUrlGenerator::SERVER_PARAMETER => $mapping->getServerName(),
98
                DiscoveryUrlGenerator::PATH_PARAMETER => $mapping->getServerPath(),
99
            ),
100
            'glob',
101
            $mapping->getUuid()
102
        );
103
104
        $this->discoveryManager->addRootBindingDescriptor(
105
            new BindingDescriptor($binding),
106
            ($flags & self::OVERRIDE) ? DiscoveryManager::OVERRIDE : 0
107
        );
108
109
        if ($this->dispatcher && $this->dispatcher->hasListeners(PuliEvents::POST_ADD_ASSET_MAPPING)) {
110
            $this->dispatcher->dispatch(
111
                PuliEvents::POST_ADD_ASSET_MAPPING,
112
                new AddAssetMappingEvent($mapping)
113
            );
114
        }
115
    }
116
117
    /**
118
     * {@inheritdoc}
119
     */
120
    public function removeRootAssetMapping(Uuid $uuid)
121
    {
122
        $mapping = null;
123
        $hasListener = $this->dispatcher && $this->dispatcher->hasListeners(PuliEvents::POST_REMOVE_ASSET_MAPPING);
124
        $expr = Expr::method('getUuid', Expr::method('toString', Expr::same($uuid->toString())))
125
            ->andX($this->exprBuilder->buildExpression());
126
127
        if ($hasListener) {
128
            // Query the mapping for the event
129
            try {
130
                $mapping = $this->getRootAssetMapping($uuid);
131
            } catch (NoSuchAssetMappingException $e) {
132
                return;
133
            }
134
        }
135
136
        $this->discoveryManager->removeRootBindingDescriptors($expr);
137
138
        if ($hasListener) {
139
            $this->dispatcher->dispatch(
140
                PuliEvents::POST_REMOVE_ASSET_MAPPING,
141
                new RemoveAssetMappingEvent($mapping)
0 ignored issues
show
Bug introduced by
It seems like $mapping defined by null on line 122 can be null; however, Puli\Manager\Api\Event\R...ingEvent::__construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
142
            );
143
        }
144
    }
145
146
    /**
147
     * {@inheritdoc}
148
     */
149 View Code Duplication
    public function removeRootAssetMappings(Expression $expr)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
150
    {
151
        $mappings = array();
152
        $hasListener = $this->dispatcher && $this->dispatcher->hasListeners(PuliEvents::POST_REMOVE_ASSET_MAPPING);
153
154
        if ($hasListener) {
155
            // Query the mappings for the event
156
            $mappings = $this->findRootAssetMappings($expr);
157
        }
158
159
        $this->discoveryManager->removeRootBindingDescriptors($this->exprBuilder->buildExpression($expr));
160
161
        if ($hasListener) {
162
            foreach ($mappings as $mapping) {
163
                $this->dispatcher->dispatch(
164
                    PuliEvents::POST_REMOVE_ASSET_MAPPING,
165
                    new RemoveAssetMappingEvent($mapping)
166
                );
167
            }
168
        }
169
    }
170
171
    /**
172
     * {@inheritdoc}
173
     */
174 View Code Duplication
    public function clearRootAssetMappings()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
175
    {
176
        $mappings = array();
177
        $hasListener = $this->dispatcher && $this->dispatcher->hasListeners(PuliEvents::POST_REMOVE_ASSET_MAPPING);
178
179
        if ($hasListener) {
180
            // Query the mappings for the event
181
            $mappings = $this->getRootAssetMappings();
182
        }
183
184
        $this->discoveryManager->removeRootBindingDescriptors($this->exprBuilder->buildExpression());
185
186
        if ($hasListener) {
187
            foreach ($mappings as $mapping) {
188
                $this->dispatcher->dispatch(
189
                    PuliEvents::POST_REMOVE_ASSET_MAPPING,
190
                    new RemoveAssetMappingEvent($mapping)
191
                );
192
            }
193
        }
194
    }
195
196
    /**
197
     * {@inheritdoc}
198
     */
199 View Code Duplication
    public function getRootAssetMapping(Uuid $uuid)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
200
    {
201
        $mappings = $this->findRootAssetMappings(Expr::method('getUuid', Expr::method('toString', Expr::same($uuid->toString()))));
202
203
        if (!$mappings) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $mappings of type Puli\Manager\Api\Asset\AssetMapping[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
204
            throw NoSuchAssetMappingException::forUuid($uuid);
205
        }
206
207
        return reset($mappings);
208
    }
209
210
    /**
211
     * {@inheritdoc}
212
     */
213
    public function getRootAssetMappings()
214
    {
215
        return $this->findRootAssetMappings(Expr::true());
216
    }
217
218
    /**
219
     * {@inheritdoc}
220
     */
221 View Code Duplication
    public function hasRootAssetMapping(Uuid $uuid)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
222
    {
223
        $expr = Expr::method('getUuid', Expr::method('toString', Expr::same($uuid->toString())))
224
            ->andX($this->exprBuilder->buildExpression());
225
226
        return $this->discoveryManager->hasRootBindingDescriptors($expr);
227
    }
228
229
    /**
230
     * {@inheritdoc}
231
     */
232
    public function hasRootAssetMappings(Expression $expr = null)
233
    {
234
        return $this->discoveryManager->hasRootBindingDescriptors($this->exprBuilder->buildExpression($expr));
235
    }
236
237
    /**
238
     * {@inheritdoc}
239
     */
240 View Code Duplication
    public function findRootAssetMappings(Expression $expr)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
241
    {
242
        $descriptors = $this->discoveryManager->findRootBindingDescriptors($this->exprBuilder->buildExpression($expr));
243
        $mappings = array();
244
245
        foreach ($descriptors as $descriptor) {
246
            $mappings[] = $this->bindingToMapping($descriptor->getBinding());
0 ignored issues
show
Documentation introduced by
$descriptor->getBinding() is of type object<Puli\Discovery\Api\Binding\Binding>, but the function expects a object<Puli\Discovery\Binding\ResourceBinding>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
247
        }
248
249
        return $mappings;
250
    }
251
252
    /**
253
     * {@inheritdoc}
254
     */
255 View Code Duplication
    public function getAssetMapping(Uuid $uuid)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
256
    {
257
        $mappings = $this->findAssetMappings(Expr::method('getUuid', Expr::method('toString', Expr::same($uuid->toString()))));
258
259
        if (!$mappings) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $mappings of type Puli\Manager\Api\Asset\AssetMapping[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
260
            throw NoSuchAssetMappingException::forUuid($uuid);
261
        }
262
263
        return reset($mappings);
264
    }
265
266
    /**
267
     * {@inheritdoc}
268
     */
269
    public function getAssetMappings()
270
    {
271
        return $this->findAssetMappings(Expr::true());
272
    }
273
274
    /**
275
     * {@inheritdoc}
276
     */
277 View Code Duplication
    public function findAssetMappings(Expression $expr)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
278
    {
279
        $descriptors = $this->discoveryManager->findBindingDescriptors($this->exprBuilder->buildExpression($expr));
280
        $mappings = array();
281
282
        foreach ($descriptors as $descriptor) {
283
            $mappings[] = $this->bindingToMapping($descriptor->getBinding());
0 ignored issues
show
Documentation introduced by
$descriptor->getBinding() is of type object<Puli\Discovery\Api\Binding\Binding>, but the function expects a object<Puli\Discovery\Binding\ResourceBinding>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
284
        }
285
286
        return $mappings;
287
    }
288
289
    /**
290
     * {@inheritdoc}
291
     */
292 View Code Duplication
    public function hasAssetMapping(Uuid $uuid)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
293
    {
294
        $expr = Expr::method('getUuid', Expr::method('toString', Expr::same($uuid->toString())))
295
            ->andX($this->exprBuilder->buildExpression());
296
297
        return $this->discoveryManager->hasBindingDescriptors($expr);
298
    }
299
300
    /**
301
     * {@inheritdoc}
302
     */
303
    public function hasAssetMappings(Expression $expr = null)
304
    {
305
        return $this->discoveryManager->hasBindingDescriptors($this->exprBuilder->buildExpression($expr));
306
    }
307
308
    private function bindingToMapping(ResourceBinding $binding)
309
    {
310
        return new AssetMapping(
311
            // Remove "{,/**/*}" suffix
312
            substr($binding->getQuery(), 0, -8),
313
            $binding->getParameterValue(DiscoveryUrlGenerator::SERVER_PARAMETER),
314
            $binding->getParameterValue(DiscoveryUrlGenerator::PATH_PARAMETER),
315
            $binding->getUuid()
316
        );
317
    }
318
}
319