Configuration::mustApplyManifestDefaultPath()   B
last analyzed

Complexity

Conditions 5
Paths 7

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 8
c 0
b 0
f 0
rs 8.8571
ccs 5
cts 5
cp 1
cc 5
eloc 5
nc 7
nop 1
crap 5
1
<?php
2
3
namespace Rj\FrontendBundle\DependencyInjection;
4
5
use Rj\FrontendBundle\Util\Util;
6
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
7
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
8
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
9
use Symfony\Component\Config\Definition\ConfigurationInterface;
10
11
class Configuration implements ConfigurationInterface
12
{
13
    const DEFAULT_PREFIX = 'assets';
14
15
    /**
16
     * @var string
17
     */
18
    private $kernelRootDir;
19
20
    /**
21
     * @param string $kernelRootDir
22
     */
23 35
    public function __construct($kernelRootDir)
24
    {
25 35
        $this->kernelRootDir = $kernelRootDir;
26 35
    }
27
28
    /**
29
     * {@inheritdoc}
30
     */
31 35
    public function getConfigTreeBuilder()
32
    {
33 35
        $self = $this;
34
35 35
        return $this->createRoot('rj_frontend', 'array')
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Symfony\Component\Config...\Builder\NodeDefinition as the method children() does only exist in the following sub-classes of Symfony\Component\Config...\Builder\NodeDefinition: Symfony\Component\Config...der\ArrayNodeDefinition. 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...
36 35
            ->children()
37 35
                ->booleanNode('override_default_package')->defaultTrue()->end()
38 35
                ->arrayNode('fallback_patterns')
39 35
                    ->prototype('scalar')->end()
40 35
                    ->defaultValue(['.*bundles\/.*'])
41 35
                ->end()
42 35
                ->append($this->addLivereloadSection())
43 35
                ->append($this->addPackagePrefixSection(self::DEFAULT_PREFIX))
44 35
                ->append($this->addPackageManifestSection())
45 35
                ->arrayNode('packages')
46 35
                    ->useAttributeAsKey('name')
47 35
                    ->prototype('array')
48 35
                        ->children()
49 35
                            ->append($this->addPackagePrefixSection())
50 35
                            ->append($this->addPackageManifestSection())
51 35
                        ->end()
52 35
                        ->beforeNormalization()
53 35
                            ->ifTrue(function ($config) use ($self) {
54 19
                                return $self->mustApplyManifestDefaultPath($config);
55 35
                            })
56 35
                            ->then(function ($config) use ($self) {
57 1
                                return $self->applyManifestDefaultPath($config);
58 35
                            })
59 35
                        ->end()
60 35
                    ->end()
61 35
                    ->validate()
62 35
                        ->ifTrue(function ($config) {
63 16
                            return in_array('default', array_keys($config));
64 35
                        })
65 35
                        ->thenInvalid("'default' is a reserved package name")
66 35
                    ->end()
67 35
                ->end()
68 35
            ->end()
69 35
            ->beforeNormalization()
70 35
                ->ifTrue(function ($config) use ($self) {
71 35
                    return $self->mustApplyManifestDefaultPath($config);
72 35
                })
73 35
                ->then(function ($config) use ($self) {
74 3
                    return $self->applyManifestDefaultPath($config);
75 35
                })
76 35
            ->end()
77 35
        ->end();
78
    }
79
80 35
    private function addLivereloadSection()
81
    {
82 35
        return $this->createRoot('livereload')
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Symfony\Component\Config...\Builder\NodeDefinition as the method canBeDisabled() does only exist in the following sub-classes of Symfony\Component\Config...\Builder\NodeDefinition: Symfony\Component\Config...der\ArrayNodeDefinition. 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...
83 35
            ->canBeDisabled()
84 35
            ->children()
85 35
                ->scalarNode('url')
86 35
                    ->defaultValue('//localhost:35729/livereload.js')
87 35
                ->end()
88 35
            ->end()
89
        ;
90
    }
91
92 35
    private function addPackagePrefixSection($defaultValue = null)
93
    {
94 35
        $node = $this->createRoot('prefix')
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Symfony\Component\Config...\Builder\NodeDefinition as the method prototype() does only exist in the following sub-classes of Symfony\Component\Config...\Builder\NodeDefinition: Symfony\Component\Config...der\ArrayNodeDefinition. 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...
95 35
            ->prototype('scalar')->end()
96 35
            ->defaultValue([$defaultValue])
97 35
            ->requiresAtLeastOneElement()
98 35
            ->beforeNormalization()
99 35
                ->ifString()
100
                ->then(function ($v) { return [$v]; })
101 35
            ->end()
102 35
            ->validate()
103 35
                ->ifTrue(function ($prefixes) {
104 23
                    return Util::containsUrl($prefixes)
105 23
                        && Util::containsNotUrl($prefixes);
106 35
                })
107 35
                ->thenInvalid('Packages cannot have both URL and path prefixes')
108 35
            ->end()
109 35
            ->validate()
110 35
                ->ifTrue(function ($prefixes) {
111 22
                    return count($prefixes) > 1
112 22
                        && Util::containsNotUrl($prefixes);
113 35
                })
114 35
                ->thenInvalid('Packages can only have one path prefix')
115 35
            ->end()
116
        ;
117
118 35
        return $defaultValue === null
119 35
            ? $node->isRequired()
120 35
            : $node
121
        ;
122
    }
123
124 35
    private function addPackageManifestSection()
125
    {
126 35
        return $this->createRoot('manifest')
127 35
            ->canBeEnabled()
128 35
            ->children()
129 35
                ->scalarNode('format')
130 35
                    ->defaultValue('json')
131 35
                    ->validate()
132 35
                        ->ifNotInArray(['json'])
133 35
                        ->thenInvalid('For the moment only JSON manifest files are supported')
134 35
                    ->end()
135 35
                ->end()
136 35
                ->scalarNode('path')->isRequired()->end()
137 35
                ->scalarNode('root_key')->defaultNull()->end()
138 35
            ->end()
139 35
            ->beforeNormalization()
140 35
                ->ifString()
141
                ->then(function ($v) { return ['enabled' => true, 'path' => $v]; })
142 35
            ->end()
143
        ;
144
    }
145
146
    /**
147
     * Returns true if the manifest's path has not been defined AND:
148
     *  - a prefix has not been defined
149
     *  - OR if a prefix has been defined, it's not a URL.
150
     *
151
     * Note that the manifest's configuration can be a string, in which case it
152
     * represents the path to the manifest file.
153
     *
154
     * This method is public because of the inability to use $this in closures
155
     * in PHP 5.3.
156
     *
157
     * @param array $config
158
     *
159
     * @return bool
160
     */
161 35
    public function mustApplyManifestDefaultPath(array $config)
162
    {
163 35
        return isset($config['manifest']) &&
164 35
            !is_string($config['manifest']) &&
165 35
            !isset($config['manifest']['path']) &&
166 35
            (!isset($config['prefix']) || !Util::containsUrl($config['prefix']))
167
        ;
168
    }
169
170
    /**
171
     * Apply a default manifest path computed from the defined prefix.
172
     *
173
     * After calling this method, the manifest's path will be
174
     * %kernel.root_dir%/../web/$prefix/manifest.json, where $prefix is the
175
     * configured prefix.
176
     *
177
     * Note that this method is used for both the default package's config and
178
     * for each custom package's config.
179
     *
180
     * This method is public because of the inability to use $this in closures
181
     * in PHP 5.3
182
     *
183
     * @param array $config
184
     *
185
     * @return array
186
     */
187 4
    public function applyManifestDefaultPath(array $config)
188
    {
189 4
        $prefix = isset($config['prefix']) ? $config['prefix'] : self::DEFAULT_PREFIX;
190
191 4
        if (is_array($prefix)) {
192
            $prefix = $prefix[0];
193
        }
194
195 4
        if (!is_array($config['manifest'])) {
196 3
            $config['manifest'] = ['enabled' => true];
197
        }
198
199 4
        $config['manifest']['path'] = implode('/', [
200 4
            $this->kernelRootDir,
201 4
            '..',
202 4
            'web',
203 4
            $prefix,
204 4
            'manifest.json',
205
        ]);
206
207 4
        return $config;
208
    }
209
210
    /**
211
     * @param string      $root
212
     * @param string|null $type
213
     *
214
     * @return ArrayNodeDefinition|NodeDefinition
215
     */
216 35
    private function createRoot($root, $type = null)
217
    {
218 35
        $treeBuilder = new TreeBuilder();
219
220 35
        if ($type !== null) {
221 35
            return $treeBuilder->root($root, $type);
222
        }
223
224 35
        return $treeBuilder->root($root);
225
    }
226
}
227