Completed
Pull Request — 7.0 (#917)
by Toni
01:35
created

MappingPass::createIndex()   B

Complexity

Conditions 5
Paths 3

Size

Total Lines 52

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 52
rs 8.7361
c 0
b 0
f 0
cc 5
nc 3
nop 2

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * This file is part of the ONGR package.
5
 *
6
 * (c) NFQ Technologies UAB <[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 ONGR\ElasticsearchBundle\DependencyInjection\Compiler;
13
14
use ONGR\ElasticsearchBundle\Annotation\Index;
15
use ONGR\ElasticsearchBundle\DependencyInjection\Configuration;
16
use ONGR\ElasticsearchBundle\Exception\DocumentIndexParserException;
17
use ONGR\ElasticsearchBundle\Mapping\Converter;
18
use ONGR\ElasticsearchBundle\Mapping\DocumentParser;
19
use ONGR\ElasticsearchBundle\Mapping\IndexSettings;
20
use ONGR\ElasticsearchBundle\Service\IndexService;
21
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
22
use Symfony\Component\DependencyInjection\Container;
23
use Symfony\Component\DependencyInjection\ContainerBuilder;
24
use Symfony\Component\DependencyInjection\Definition;
25
26
class MappingPass implements CompilerPassInterface
27
{
28
    /**
29
     * @var array
30
     */
31
    private $indexes = [];
32
33
    /**
34
     * @var string
35
     */
36
    private $defaultIndex = null;
37
38
    public function process(ContainerBuilder $container)
39
    {
40
        $kernelDir = $container->getParameter('kernel.project_dir');
41
        $parser = $container->get(DocumentParser::class);
42
43
        $indexClasses = [];
44
        $indexSettingsArray = [];
45
        foreach ($container->getParameter(Configuration::ONGR_SOURCE_DIR) as $dir) {
46
            foreach ($this->getNamespaces($kernelDir . $dir) as $namespace) {
47
                $indexClasses[$namespace] = $namespace;
48
            }
49
        }
50
51
        $overwrittenClasses = [];
52
        $indexOverrides = $container->getParameter(Configuration::ONGR_INDEXES_OVERRIDE);
53
54
        foreach ($indexOverrides as $name => $indexOverride) {
55
            $class = isset($indexOverride['class']) ? $indexOverride['class'] : $name;
56
57
            if (!isset($indexClasses[$class])) {
58
                throw new \RuntimeException(
59
                    sprintf(
60
                        'Document `%s` defined in ongr_elasticsearch.indexes config could not been found',
61
                        $class
62
                    )
63
                );
64
            }
65
66
            $indexSettings = $this->parseIndexSettingsFromClass($parser, $class);
0 ignored issues
show
Documentation introduced by
$parser is of type object|null, but the function expects a object<ONGR\Elasticsearc...Mapping\DocumentParser>.

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...
67
68
            if ($class !== $name) {
69
                $indexSettings->setIndexName('ongr.es.index.'.$name);
70
            }
71
72
            if (isset($indexOverride['alias'])) {
73
                $indexSettings->setAlias($indexOverride['alias']);
74
            }
75
76
            if (isset($indexOverride['settings'])) {
77
                $indexSettings->setIndexMetadata($indexOverride['settings']);
78
            }
79
80
            if (isset($indexOverride['hosts'])) {
81
                $indexSettings->setHosts($indexOverride['hosts']);
82
            }
83
84
            if (isset($indexOverride['default'])) {
85
                $indexSettings->setDefaultIndex($indexOverride['default']);
86
            }
87
88
            $indexSettingsArray[$name] = $indexSettings;
89
            $overwrittenClasses[$class] = $class;
90
        }
91
92
        foreach (array_diff($indexClasses, $overwrittenClasses) as $indexClass) {
93
            try {
94
                $indexSettingsArray[$indexClass] = $this->parseIndexSettingsFromClass($parser, $indexClass);
0 ignored issues
show
Documentation introduced by
$parser is of type object|null, but the function expects a object<ONGR\Elasticsearc...Mapping\DocumentParser>.

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...
95
            } catch (DocumentIndexParserException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
96
            }
97
        }
98
99
        foreach ($indexSettingsArray as $indexSettings) {
100
            $this->createIndex($container, $indexSettings);
101
        }
102
103
        $container->setParameter(Configuration::ONGR_INDEXES, $this->indexes);
104
        $container->setParameter(
105
            Configuration::ONGR_DEFAULT_INDEX,
106
            $this->defaultIndex ?? current(array_keys($this->indexes))
107
        );
108
    }
109
110
    private function parseIndexSettingsFromClass(DocumentParser $parser, string $className) : IndexSettings
111
    {
112
        $class = new \ReflectionClass($className);
113
114
        /** @var Index $document */
115
        $document = $parser->getIndexAnnotation($class);
116
117
        if ($document === null) {
118
            throw new DocumentIndexParserException();
119
        }
120
121
        $indexSettings = new IndexSettings(
122
            $className,
123
            $className,
124
            $parser->getIndexAliasName($class),
125
            $parser->getIndexMetadata($class),
126
            $parser->getPropertyMetadata($class),
127
            $document->hosts,
128
            $parser->isDefaultIndex($class)
129
        );
130
131
        $indexSettings->setIndexMetadata(['settings' => [
132
            'number_of_replicas' => $document->numberOfReplicas,
133
            'number_of_shards' => $document->numberOfShards,
134
        ]]);
135
136
        return $indexSettings;
137
    }
138
139
    private function createIndex(Container $container, IndexSettings $indexSettings)
140
    {
141
        $converterDefinition = $container->getDefinition(Converter::class);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Symfony\Component\DependencyInjection\Container as the method getDefinition() does only exist in the following sub-classes of Symfony\Component\DependencyInjection\Container: Symfony\Component\Depend...urationContainerBuilder, Symfony\Component\Depend...ection\ContainerBuilder. 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...
142
143
        $indexSettingsDefinition = new Definition(
144
            IndexSettings::class,
145
            [
146
                $indexSettings->getNamespace(),
147
                $indexSettings->getAlias(),
148
                $indexSettings->getAlias(),
149
                $indexSettings->getIndexMetadata(),
150
                $indexSettings->getPropertyMetadata(),
151
                $indexSettings->getHosts(),
152
                $indexSettings->isDefaultIndex(),
153
            ]
154
        );
155
156
        $indexServiceDefinition = new Definition(IndexService::class, [
157
            $indexSettings->getNamespace(),
158
            $converterDefinition,
159
            $container->getDefinition('event_dispatcher'),
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Symfony\Component\DependencyInjection\Container as the method getDefinition() does only exist in the following sub-classes of Symfony\Component\DependencyInjection\Container: Symfony\Component\Depend...urationContainerBuilder, Symfony\Component\Depend...ection\ContainerBuilder. 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...
160
            $indexSettingsDefinition,
161
            $container->getParameter(Configuration::ONGR_PROFILER_CONFIG)
162
                ? $container->getDefinition('ongr.esb.tracer') : null
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Symfony\Component\DependencyInjection\Container as the method getDefinition() does only exist in the following sub-classes of Symfony\Component\DependencyInjection\Container: Symfony\Component\Depend...urationContainerBuilder, Symfony\Component\Depend...ection\ContainerBuilder. 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...
163
        ]);
164
165
        $indexServiceDefinition->setPublic(true);
166
        $converterDefinition->addMethodCall(
167
            'addClassMetadata',
168
            [
169
                $indexSettings->getNamespace(),
170
                $indexSettings->getPropertyMetadata()
171
            ]
172
        );
173
174
        $container->setDefinition($indexSettings->getIndexName(), $indexServiceDefinition);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Symfony\Component\DependencyInjection\Container as the method setDefinition() does only exist in the following sub-classes of Symfony\Component\DependencyInjection\Container: Symfony\Component\Depend...urationContainerBuilder, Symfony\Component\Depend...ection\ContainerBuilder. 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...
175
        $this->indexes[$indexSettings->getAlias()] = $indexSettings->getIndexName();
176
        $isCurrentIndexDefault = $indexSettings->isDefaultIndex();
177
        if ($this->defaultIndex && $isCurrentIndexDefault) {
178
            throw new \RuntimeException(
179
                sprintf(
180
                    'Only one index can be set as default. We found 2 indexes as default ones `%s` and `%s`',
181
                    $this->defaultIndex,
182
                    $indexSettings->getAlias()
183
                )
184
            );
185
        }
186
187
        if ($isCurrentIndexDefault) {
188
            $this->defaultIndex = $indexSettings->getAlias();
189
        }
190
    }
191
192
    private function getNamespaces($directory): array
193
    {
194
        if (!is_dir($directory)) {
195
            return [];
196
        }
197
198
        $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($directory));
199
        $files = new \RegexIterator($iterator, '/^.+\.php$/i', \RecursiveRegexIterator::GET_MATCH);
200
201
        $documents = [];
202
203
        foreach ($files as $file => $v) {
204
            $documents[] = $this->getFullNamespace($file) . '\\' . $this->getClassname($file);
205
        }
206
207
        return $documents;
208
    }
209
210
    private function getFullNamespace($filename)
211
    {
212
        $lines = preg_grep('/^namespace /', file($filename));
213
        $namespaceLine = array_shift($lines);
214
        $match = array();
215
        preg_match('/^namespace (.*);$/', $namespaceLine, $match);
216
        $fullNamespace = array_pop($match);
217
218
        return $fullNamespace;
219
    }
220
221
    private function getClassname($filename)
222
    {
223
        $directoriesAndFilename = explode('/', $filename);
224
        $filename = array_pop($directoriesAndFilename);
225
        $nameAndExtension = explode('.', $filename);
226
        $className = array_shift($nameAndExtension);
227
228
        return $className;
229
    }
230
}
231