Completed
Pull Request — master (#917)
by Toni
01:49
created

MappingPass::getClassname()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.9666
c 0
b 0
f 0
cc 1
nc 1
nop 1
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
        foreach($indexSettingsArray as $indexSettings) {
99
            $this->createIndex($container, $indexSettings);
100
        }
101
102
        $container->setParameter(Configuration::ONGR_INDEXES, $this->indexes);
103
        $container->setParameter(
104
            Configuration::ONGR_DEFAULT_INDEX,
105
            $this->defaultIndex ?? current(array_keys($this->indexes))
106
        );
107
    }
108
109
    private function parseIndexSettingsFromClass(DocumentParser $parser, string $className) : IndexSettings
110
    {
111
        $class = new \ReflectionClass($className);
112
113
        /** @var Index $document */
114
        $document = $parser->getIndexAnnotation($class);
115
116
        if ($document === null) {
117
            throw new DocumentIndexParserException();
118
        }
119
120
        $indexSettings = new IndexSettings(
121
            $className,
122
            $className,
123
            $parser->getIndexAliasName($class),
124
            $parser->getIndexMetadata($class),
125
            $parser->getPropertyMetadata($class),
126
            $document->hosts,
127
            $parser->isDefaultIndex($class)
128
        );
129
130
        $indexSettings->setIndexMetadata(['settings' => [
131
            'number_of_replicas' => $document->numberOfReplicas,
132
            'number_of_shards' => $document->numberOfShards,
133
        ]]);
134
135
        return $indexSettings;
136
    }
137
138
    private function createIndex(Container $container, IndexSettings $indexSettings) {
139
        $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...
140
141
        $indexSettingsDefinition = new Definition(
142
            IndexSettings::class,
143
            [
144
                $indexSettings->getNamespace(),
145
                $indexSettings->getAlias(),
146
                $indexSettings->getAlias(),
147
                $indexSettings->getIndexMetadata(),
148
                $indexSettings->getPropertyMetadata(),
149
                $indexSettings->getHosts(),
150
                $indexSettings->isDefaultIndex(),
151
            ]
152
        );
153
154
        $indexServiceDefinition = new Definition(IndexService::class, [
155
            $indexSettings->getNamespace(),
156
            $converterDefinition,
157
            $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...
158
            $indexSettingsDefinition,
159
            $container->getParameter(Configuration::ONGR_PROFILER_CONFIG)
160
                ? $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...
161
        ]);
162
163
        $indexServiceDefinition->setPublic(true);
164
        $converterDefinition->addMethodCall(
165
            'addClassMetadata',
166
            [
167
                $indexSettings->getNamespace(),
168
                $indexSettings->getPropertyMetadata()
169
            ]
170
        );
171
172
        $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...
173
        $this->indexes[$indexSettings->getAlias()] = $indexSettings->getIndexName();
174
        $isCurrentIndexDefault = $indexSettings->isDefaultIndex();
175
        if ($this->defaultIndex && $isCurrentIndexDefault) {
176
            throw new \RuntimeException(
177
                sprintf(
178
                    'Only one index can be set as default. We found 2 indexes as default ones `%s` and `%s`',
179
                    $this->defaultIndex,
180
                    $indexSettings->getAlias()
181
                )
182
            );
183
        }
184
185
        if ($isCurrentIndexDefault) {
186
            $this->defaultIndex = $indexSettings->getAlias();
187
        }
188
    }
189
190
    private function getNamespaces($directory): array
191
    {
192
        if (!is_dir($directory)) {
193
            return [];
194
        }
195
196
        $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($directory));
197
        $files = new \RegexIterator($iterator, '/^.+\.php$/i', \RecursiveRegexIterator::GET_MATCH);
198
199
        $documents = [];
200
201
        foreach ($files as $file => $v) {
202
            $documents[] = $this->getFullNamespace($file) . '\\' . $this->getClassname($file);
203
        }
204
205
        return $documents;
206
    }
207
208
    private function getFullNamespace($filename)
209
    {
210
        $lines = preg_grep('/^namespace /', file($filename));
211
        $namespaceLine = array_shift($lines);
212
        $match = array();
213
        preg_match('/^namespace (.*);$/', $namespaceLine, $match);
214
        $fullNamespace = array_pop($match);
215
216
        return $fullNamespace;
217
    }
218
219
    private function getClassname($filename)
220
    {
221
        $directoriesAndFilename = explode('/', $filename);
222
        $filename = array_pop($directoriesAndFilename);
223
        $nameAndExtension = explode('.', $filename);
224
        $className = array_shift($nameAndExtension);
225
226
        return $className;
227
    }
228
}
229