Completed
Push — rest_bdd_errors ( ddfd0b...0f9ef1 )
by André
26:47
created

IO::addSemanticConfig()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 21
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 19
nc 1
nop 1
dl 0
loc 21
rs 9.3142
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
                ->end()
48
            ->end();
49
    }
50
51
    public function mapConfig(array &$scopeSettings, $currentScope, ContextualizerInterface $contextualizer)
52
    {
53
        if (!isset($scopeSettings['io'])) {
54
            return;
55
        }
56
57
        $settings = $scopeSettings['io'];
58
        if (isset($settings['metadata_handler'])) {
59
            $contextualizer->setContextualParameter('io.metadata_handler', $currentScope, $settings['metadata_handler']);
60
        }
61
        if (isset($settings['binarydata_handler'])) {
62
            $contextualizer->setContextualParameter('io.binarydata_handler', $currentScope, $settings['binarydata_handler']);
63
        }
64
        if (isset($settings['url_prefix'])) {
65
            $contextualizer->setContextualParameter('io.url_prefix', $currentScope, $settings['url_prefix']);
66
        }
67
    }
68
69
    /**
70
     * Post process configuration to add io_root_dir and io_prefix.
71
     */
72
    public function postMap(array $config, ContextualizerInterface $contextualizer)
73
    {
74
        $container = $contextualizer->getContainer();
75
76
        // complex parameters dependencies
77
        foreach (array_merge($config['siteaccess']['list'], array_keys($config['siteaccess']['groups'])) as $scope) {
78
            $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...
79
            $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...
80
            $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...
81
        }
82
83
        // we should only write for default, and for sa/sa groups/global IF they have a declared value
84
        $scopes = array_merge(
85
            array('default'),
86
            $config['siteaccess']['list'],
87
            array_keys($config['siteaccess']['groups'])
88
        );
89
        foreach ($scopes as $scope) {
90
            // post process io.url_prefix for complex settings
91
            $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...
92
            if (is_string($postProcessedValue)) {
93
                $contextualizer->setContextualParameter('io.url_prefix', $scope, $postProcessedValue);
94
            }
95
96
            // post process io.legacy_url_prefix for complex settings
97
            $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...
98
            if (is_string($postProcessedValue)) {
99
                $contextualizer->setContextualParameter('io.legacy_url_prefix', $scope, $postProcessedValue);
100
            }
101
102
            // post process io.root_dir for complex settings
103
            $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...
104
            if (is_string($postProcessedValue)) {
105
                $contextualizer->setContextualParameter('io.root_dir', $scope, $postProcessedValue);
106
            }
107
        }
108
    }
109
110
    /**
111
     * Applies dependencies of complex $parameter in $scope.
112
     */
113
    private function addComplexParametersDependencies($parameter, $scope, ContainerBuilder $container)
114
    {
115
        // The complex setting exists in this scope, we don't need to do anything
116
        if ($container->hasParameter("ezsettings.$scope.$parameter")) {
117
            return;
118
        }
119
        $parameterValue = $container->getParameter("ezsettings.default.$parameter");
120
121
        // not complex in this scope
122
        if (!$this->complexSettingParser->containsDynamicSettings($parameterValue)) {
123
            return;
124
        }
125
126
        // if one of the complex parameters dependencies is set in the current scope,
127
        // we set the complex parameter in the current scope as well.
128
        foreach ($this->complexSettingParser->parseComplexSetting($parameterValue) as $dynamicParameter) {
129
            $dynamicParameterParts = $this->complexSettingParser->parseDynamicSetting($dynamicParameter);
130
            if ($dynamicParameterParts['scope'] === $scope) {
131
                continue;
132
            }
133
            $dynamicParameterId = sprintf(
134
                '%s.%s.%s',
135
                $dynamicParameterParts['namespace'] ?: 'ezsettings',
136
                $scope,
137
                $dynamicParameterParts['param']
138
            );
139
            if ($container->hasParameter($dynamicParameterId)) {
140
                $container->setParameter("ezsettings.$scope.$parameter", $parameterValue);
141
                break;
142
            }
143
        }
144
    }
145
146
    private function postProcessComplexSetting($setting, $sa, ContainerBuilder $container)
147
    {
148
        $configResolver = $container->get('ezpublish.config.resolver.core');
149
150
        if (!$configResolver->hasParameter($setting, null, $sa)) {
151
            return false;
152
        }
153
154
        $settingValue = $configResolver->getParameter($setting, null, $sa);
155
        if (!$this->complexSettingParser->containsDynamicSettings($settingValue)) {
156
            return false;
157
        }
158
159
        // we kind of need to process this as well, don't we ?
160
        if ($this->complexSettingParser->isDynamicSetting($settingValue)) {
161
            $parts = $this->complexSettingParser->parseDynamicSetting($settingValue);
162
            if (!isset($parts['namespace'])) {
163
                $parts['namespace'] = 'ezsettings';
164
            }
165
            if (!isset($parts['scope'])) {
166
                $parts['scope'] = $sa;
167
            }
168
169
            return $configResolver->getParameter($parts['param'], null, $sa);
170
        }
171
172
        $value = $settingValue;
173
        foreach ($this->complexSettingParser->parseComplexSetting($settingValue) as $dynamicSetting) {
174
            $parts = $this->complexSettingParser->parseDynamicSetting($dynamicSetting);
175
            if (!isset($parts['namespace'])) {
176
                $parts['namespace'] = 'ezsettings';
177
            }
178
            if (!isset($parts['scope'])) {
179
                $parts['scope'] = $sa;
180
            }
181
182
            $dynamicSettingValue = $configResolver->getParameter($parts['param'], $parts['namespace'], $parts['scope']);
183
184
            $value = str_replace($dynamicSetting, $dynamicSettingValue, $value);
185
        }
186
187
        return $value;
188
    }
189
}
190