Completed
Push — ezp26175-exception_on_non_defa... ( 77d2f3...ca5fc8 )
by
unknown
39:33
created

IO::mapConfig()   C

Complexity

Conditions 8
Paths 41

Size

Total Lines 25
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 15
nc 41
nop 3
dl 0
loc 25
rs 5.3846
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * This file is part of the eZ Publish Kernel package.
5
 *
6
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
7
 * @license For full copyright and license information view LICENSE file distributed with this source code.
8
 *
9
 * @version //autogentag//
10
 */
11
namespace eZ\Bundle\EzPublishCoreBundle\DependencyInjection\Configuration\Parser;
12
13
use eZ\Bundle\EzPublishCoreBundle\DependencyInjection\Configuration\AbstractParser;
14
use eZ\Bundle\EzPublishCoreBundle\DependencyInjection\Configuration\ComplexSettings\ComplexSettingParserInterface;
15
use eZ\Bundle\EzPublishCoreBundle\DependencyInjection\Configuration\SiteAccessAware\ContextualizerInterface;
16
use Symfony\Component\Config\Definition\Builder\NodeBuilder;
17
use Symfony\Component\DependencyInjection\ContainerBuilder;
18
19
class IO extends AbstractParser
20
{
21
    /** @var ComplexSettingParserInterface */
22
    private $complexSettingParser;
23
24
    public function __construct(ComplexSettingParserInterface $complexSettingParser)
25
    {
26
        $this->complexSettingParser = $complexSettingParser;
27
    }
28
29
    public function addSemanticConfig(NodeBuilder $nodeBuilder)
30
    {
31
        $nodeBuilder
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...
32
            ->arrayNode('io')
33
                ->info('Binary storage options')
34
                ->children()
35
                    ->scalarNode('metadata_handler')
36
                        ->info('Handler uses to manipulate IO files metadata')
37
                        ->example('default')
38
                    ->end()
39
                    ->scalarNode('binarydata_handler')
40
                        ->info('Handler uses to manipulate IO files binarydata')
41
                        ->example('default')
42
                    ->end()
43
                    ->scalarNode('url_prefix')
44
                        ->info('Prefix added to binary files uris. A host can also be added')
45
                        ->example('$var_dir$/$storage_dir$, http://static.example.com/')
46
                    ->end()
47
                    ->arrayNode('permissions')
48
                        ->info('Permissions applied by the Local flysystem adapter when creating content files and directories in storage.')
49
                        ->children()
50
                            ->scalarNode('files')
51
                                ->defaultValue('0644')
52
                            ->end()
53
                            ->scalarNode('directories')
54
                                ->defaultValue('0755')
55
                            ->end()
56
                        ->end()
57
                    ->end()
58
                ->end()
59
            ->end();
60
    }
61
62
    public function mapConfig(array &$scopeSettings, $currentScope, ContextualizerInterface $contextualizer)
63
    {
64
        if (!isset($scopeSettings['io'])) {
65
            return;
66
        }
67
68
        $settings = $scopeSettings['io'];
69
        if (isset($settings['metadata_handler'])) {
70
            $contextualizer->setContextualParameter('io.metadata_handler', $currentScope, $settings['metadata_handler']);
71
        }
72
        if (isset($settings['binarydata_handler'])) {
73
            $contextualizer->setContextualParameter('io.binarydata_handler', $currentScope, $settings['binarydata_handler']);
74
        }
75
        if (isset($settings['url_prefix'])) {
76
            $contextualizer->setContextualParameter('io.url_prefix', $currentScope, $settings['url_prefix']);
77
        }
78
        if (isset($settings['permissions'])) {
79
            if (isset($settings['permissions']['files'])) {
80
                $contextualizer->setContextualParameter('io.permissions.files', $currentScope, $settings['permissions']['files']);
81
            }
82
            if (isset($settings['permissions']['directories'])) {
83
                $contextualizer->setContextualParameter('io.permissions.directories', $currentScope, $settings['permissions']['directories']);
84
            }
85
        }
86
    }
87
88
    /**
89
     * Post process configuration to add io_root_dir and io_prefix.
90
     */
91
    public function postMap(array $config, ContextualizerInterface $contextualizer)
92
    {
93
        $container = $contextualizer->getContainer();
94
95
        // complex parameters dependencies
96
        foreach (array_merge($config['siteaccess']['list'], array_keys($config['siteaccess']['groups'])) as $scope) {
97
            $this->addComplexParametersDependencies('io.url_prefix', $scope, $container);
0 ignored issues
show
Compatibility introduced by
$container of type object<Symfony\Component...ion\ContainerInterface> is not a sub-type of object<Symfony\Component...ction\ContainerBuilder>. It seems like you assume a concrete implementation of the interface Symfony\Component\Depend...tion\ContainerInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
98
            $this->addComplexParametersDependencies('io.legacy_url_prefix', $scope, $container);
0 ignored issues
show
Compatibility introduced by
$container of type object<Symfony\Component...ion\ContainerInterface> is not a sub-type of object<Symfony\Component...ction\ContainerBuilder>. It seems like you assume a concrete implementation of the interface Symfony\Component\Depend...tion\ContainerInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
99
            $this->addComplexParametersDependencies('io.root_dir', $scope, $container);
0 ignored issues
show
Compatibility introduced by
$container of type object<Symfony\Component...ion\ContainerInterface> is not a sub-type of object<Symfony\Component...ction\ContainerBuilder>. It seems like you assume a concrete implementation of the interface Symfony\Component\Depend...tion\ContainerInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
100
        }
101
102
        // we should only write for default, and for sa/sa groups/global IF they have a declared value
103
        $scopes = array_merge(
104
            array('default'),
105
            $config['siteaccess']['list'],
106
            array_keys($config['siteaccess']['groups'])
107
        );
108
        foreach ($scopes as $scope) {
109
            // post process io.url_prefix for complex settings
110
            $postProcessedValue = $this->postProcessComplexSetting('io.url_prefix', $scope, $container);
0 ignored issues
show
Compatibility introduced by
$container of type object<Symfony\Component...ion\ContainerInterface> is not a sub-type of object<Symfony\Component...ction\ContainerBuilder>. It seems like you assume a concrete implementation of the interface Symfony\Component\Depend...tion\ContainerInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
111
            if (is_string($postProcessedValue)) {
112
                $contextualizer->setContextualParameter('io.url_prefix', $scope, $postProcessedValue);
113
            }
114
115
            // post process io.legacy_url_prefix for complex settings
116
            $postProcessedValue = $this->postProcessComplexSetting('io.legacy_url_prefix', $scope, $container);
0 ignored issues
show
Compatibility introduced by
$container of type object<Symfony\Component...ion\ContainerInterface> is not a sub-type of object<Symfony\Component...ction\ContainerBuilder>. It seems like you assume a concrete implementation of the interface Symfony\Component\Depend...tion\ContainerInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
117
            if (is_string($postProcessedValue)) {
118
                $contextualizer->setContextualParameter('io.legacy_url_prefix', $scope, $postProcessedValue);
119
            }
120
121
            // post process io.root_dir for complex settings
122
            $postProcessedValue = $this->postProcessComplexSetting('io.root_dir', $scope, $container);
0 ignored issues
show
Compatibility introduced by
$container of type object<Symfony\Component...ion\ContainerInterface> is not a sub-type of object<Symfony\Component...ction\ContainerBuilder>. It seems like you assume a concrete implementation of the interface Symfony\Component\Depend...tion\ContainerInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
123
            if (is_string($postProcessedValue)) {
124
                $contextualizer->setContextualParameter('io.root_dir', $scope, $postProcessedValue);
125
            }
126
        }
127
    }
128
129
    /**
130
     * Applies dependencies of complex $parameter in $scope.
131
     */
132
    private function addComplexParametersDependencies($parameter, $scope, ContainerBuilder $container)
133
    {
134
        // The complex setting exists in this scope, we don't need to do anything
135
        if ($container->hasParameter("ezsettings.$scope.$parameter")) {
136
            return;
137
        }
138
        $parameterValue = $container->getParameter("ezsettings.default.$parameter");
139
140
        // not complex in this scope
141
        if (!$this->complexSettingParser->containsDynamicSettings($parameterValue)) {
142
            return;
143
        }
144
145
        // if one of the complex parameters dependencies is set in the current scope,
146
        // we set the complex parameter in the current scope as well.
147
        foreach ($this->complexSettingParser->parseComplexSetting($parameterValue) as $dynamicParameter) {
148
            $dynamicParameterParts = $this->complexSettingParser->parseDynamicSetting($dynamicParameter);
149
            if ($dynamicParameterParts['scope'] === $scope) {
150
                continue;
151
            }
152
            $dynamicParameterId = sprintf(
153
                '%s.%s.%s',
154
                $dynamicParameterParts['namespace'] ?: 'ezsettings',
155
                $scope,
156
                $dynamicParameterParts['param']
157
            );
158
            if ($container->hasParameter($dynamicParameterId)) {
159
                $container->setParameter("ezsettings.$scope.$parameter", $parameterValue);
160
                break;
161
            }
162
        }
163
    }
164
165
    private function postProcessComplexSetting($setting, $sa, ContainerBuilder $container)
166
    {
167
        $configResolver = $container->get('ezpublish.config.resolver.core');
168
169
        if (!$configResolver->hasParameter($setting, null, $sa)) {
170
            return false;
171
        }
172
173
        $settingValue = $configResolver->getParameter($setting, null, $sa);
174
        if (!$this->complexSettingParser->containsDynamicSettings($settingValue)) {
175
            return false;
176
        }
177
178
        // we kind of need to process this as well, don't we ?
179
        if ($this->complexSettingParser->isDynamicSetting($settingValue)) {
180
            $parts = $this->complexSettingParser->parseDynamicSetting($settingValue);
181
            if (!isset($parts['namespace'])) {
182
                $parts['namespace'] = 'ezsettings';
183
            }
184
            if (!isset($parts['scope'])) {
185
                $parts['scope'] = $sa;
186
            }
187
188
            return $configResolver->getParameter($parts['param'], null, $sa);
189
        }
190
191
        $value = $settingValue;
192
        foreach ($this->complexSettingParser->parseComplexSetting($settingValue) as $dynamicSetting) {
193
            $parts = $this->complexSettingParser->parseDynamicSetting($dynamicSetting);
194
            if (!isset($parts['namespace'])) {
195
                $parts['namespace'] = 'ezsettings';
196
            }
197
            if (!isset($parts['scope'])) {
198
                $parts['scope'] = $sa;
199
            }
200
201
            $dynamicSettingValue = $configResolver->getParameter($parts['param'], $parts['namespace'], $parts['scope']);
202
203
            $value = str_replace($dynamicSetting, $dynamicSettingValue, $value);
204
        }
205
206
        return $value;
207
    }
208
}
209