GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( 2e841b...0c12b0 )
by Steevan
02:56
created

ReadOnlyHydrator::getUsedProperties()   C

Complexity

Conditions 8
Paths 6

Size

Total Lines 27
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 27
rs 5.3846
cc 8
eloc 18
nc 6
nop 2
1
<?php
2
3
namespace steevanb\DoctrineReadOnlyHydrator\Hydrator;
4
5
use Doctrine\Common\Proxy\ProxyGenerator;
6
use Doctrine\ORM\Mapping\ClassMetadata;
7
use steevanb\DoctrineReadOnlyHydrator\Entity\ReadOnlyEntityInterface;
8
use steevanb\DoctrineReadOnlyHydrator\Exception\PrivateMethodShouldNotAccessPropertiesException;
9
10
class ReadOnlyHydrator extends SimpleObjectHydrator
1 ignored issue
show
Comprehensibility Best Practice introduced by
The type steevanb\DoctrineReadOnl...drator\ReadOnlyHydrator has been defined more than once; this definition is ignored, only the first definition in ComposerOverloadClass/Hy...or/ReadOnlyHydrator.php (L7-10) is considered.

This check looks for classes that have been defined more than once.

If you can, we would recommend to use standard object-oriented programming techniques. For example, to avoid multiple types, it might make sense to create a common interface, and then multiple, different implementations for that interface.

This also has the side-effect of providing you with better IDE auto-completion, static analysis and also better OPCode caching from PHP.

Loading history...
11
{
12
    const HYDRATOR_NAME = 'readOnly';
13
14
    /** @var string[] */
15
    protected $proxyFilePathsCache = [];
16
17
    /** @var string[] */
18
    protected $proxyNamespacesCache = [];
19
20
    /** @var string[] */
21
    protected $proxyClassNamesCache = [];
22
23
    /**
24
     * @param ClassMetadata $classMetaData
25
     * @param array $data
26
     * @return mixed
27
     * @throws \Exception
28
     */
29
    protected function createEntity(ClassMetadata $classMetaData, array $data)
30
    {
31
        $className = $this->getEntityClassName($classMetaData, $data);
32
        $this->generateProxyFile($classMetaData, $data);
33
34
        require_once($this->getProxyFilePath($className));
35
        $proxyClassName = $this->getProxyNamespace($className) . '\\' . $this->getProxyClassName($className);
36
        $entity = new $proxyClassName(array_keys($data));
37
38
        return $entity;
39
    }
40
41
    /**
42
     * @param ClassMetadata $classMetaData
43
     * @param array $data
44
     * @return $this
45
     */
46
    protected function generateProxyFile(ClassMetadata $classMetaData, array $data)
47
    {
48
        $entityClassName = $this->getEntityClassName($classMetaData, $data);
49
        $proxyFilePath = $this->getProxyFilePath($entityClassName);
50
        if (file_exists($proxyFilePath) === false) {
51
            $proxyMethodsCode = implode("\n\n", $this->getPhpForProxyMethods($classMetaData, $entityClassName));
52
            $proxyNamespace = $this->getProxyNamespace($entityClassName);
53
            $proxyClassName = $this->getProxyClassName($entityClassName);
54
            $generator = static::class;
55
            $readOnlyInterface = ReadOnlyEntityInterface::class;
56
57
            $php = <<<PHP
58
    <?php
59
60
    namespace $proxyNamespace;
61
62
    /**
63
     * DO NOT EDIT THIS FILE - IT WAS CREATED BY $generator
64
     */
65
    class $proxyClassName extends \\$entityClassName implements \\$readOnlyInterface
66
    {
67
        protected \$loadedProperties;
68
69
        public function __construct(array \$loadedProperties)
70
        {
71
            \$this->loadedProperties = \$loadedProperties;
72
        }
73
74
    $proxyMethodsCode
75
76
        protected function assertReadOnlyPropertiesAreLoaded(array \$properties)
77
        {
78
            foreach (\$properties as \$property) {
79
                if (in_array(\$property, \$this->loadedProperties) === false) {
80
                    throw new \steevanb\DoctrineReadOnlyHydrator\Exception\PropertyNotLoadedException(\$this, \$property);
81
                }
82
            }
83
        }
84
    }
85
PHP;
86
            file_put_contents($proxyFilePath, $php);
87
        }
88
89
        return $this;
90
    }
91
92
    /**
93
     * @param string $entityClassName
94
     * @return string
95
     */
96
    public function getProxyFilePath($entityClassName)
97
    {
98
        if (isset($this->proxyFilePathsCache[$entityClassName]) === false) {
99
            $fileName = str_replace('\\', '_', $entityClassName) . '.php';
100
            $this->proxyFilePathsCache[$entityClassName] = $this->getProxyDirectory() . DIRECTORY_SEPARATOR . $fileName;
101
        }
102
103
        return $this->proxyFilePathsCache[$entityClassName];
104
    }
105
106
    /**
107
     * @param string $entityClassName
108
     * @return string
109
     */
110 View Code Duplication
    protected function getProxyNamespace($entityClassName)
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
111
    {
112
        if (isset($this->proxyNamespacesCache[$entityClassName]) == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
113
            $this->proxyNamespacesCache[$entityClassName] =
114
                'ReadOnlyProxies\\' . substr($entityClassName, 0, strrpos($entityClassName, '\\'));
115
        }
116
117
        return $this->proxyNamespacesCache[$entityClassName];
118
    }
119
120
    /**
121
     * @param string $entityClassName
122
     * @return string
123
     */
124 View Code Duplication
    protected function getProxyClassName($entityClassName)
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
125
    {
126
        if (isset($this->proxyClassNamesCache[$entityClassName]) === false) {
127
            $this->proxyClassNamesCache[$entityClassName] =
128
                substr($entityClassName, strrpos($entityClassName, '\\') + 1);
129
        }
130
131
        return $this->proxyClassNamesCache[$entityClassName];
132
    }
133
134
    /**
135
     * As Doctrine\ORM\EntityManager::newHydrator() call new FooHydrator($this), we can't set parameters to Hydrator.
136
     * So, we will use proxyDirectory from Doctrine\Common\Proxy\AbstractProxyFactory.
137
     * It's directory used by Doctrine\ORM\Internal\Hydration\ObjectHydrator.
138
     *
139
     * @return string
140
     */
141
    protected function getProxyDirectory()
142
    {
143
        /** @var ProxyGenerator $proxyGenerator */
144
        $proxyGenerator = $this->getPrivatePropertyValue($this->_em->getProxyFactory(), 'proxyGenerator');
145
146
        $directory = $this->getPrivatePropertyValue($proxyGenerator, 'proxyDirectory');
147
        $readOnlyDirectory = $directory . DIRECTORY_SEPARATOR . 'ReadOnly';
148
        if (is_dir($readOnlyDirectory) === false) {
149
            mkdir($readOnlyDirectory);
150
        }
151
152
        return $readOnlyDirectory;
153
    }
154
155
    /**
156
     * @param \ReflectionMethod $reflectionMethod
157
     * @param array $properties
158
     * @return string|false
159
     */
160
    protected function getUsedProperties(\ReflectionMethod $reflectionMethod, $properties)
161
    {
162
        $classLines = file($reflectionMethod->getFileName());
163
        $methodLines = array_slice(
164
            $classLines,
165
            $reflectionMethod->getStartLine() - 1,
166
            $reflectionMethod->getEndLine() - $reflectionMethod->getStartLine() + 1
167
        );
168
        $code = '<?php' . "\n" . implode("\n", $methodLines) . "\n" . '?>';
169
170
        $return = array();
171
        $nextStringIsProperty = false;
172
        foreach (token_get_all($code) as $token) {
173
            if (is_array($token)) {
174
                if ($token[0] === T_VARIABLE && $token[1] === '$this') {
175
                    $nextStringIsProperty = true;
176
                } elseif ($nextStringIsProperty && $token[0] === T_STRING) {
177
                    $nextStringIsProperty = false;
178
                    if (in_array($token[1], $properties)) {
179
                        $return[$token[1]] = true;
180
                    }
181
                }
182
            }
183
        }
184
185
        return array_keys($return);
186
    }
187
188
    /**
189
     * @param ClassMetadata $classMetaData
190
     * @param string $entityClassName
191
     * @return array
192
     * @throws PrivateMethodShouldNotAccessPropertiesException
193
     */
194
    protected function getPhpForProxyMethods(ClassMetadata $classMetaData, $entityClassName)
195
    {
196
        $return = array();
197
        $reflectionClass = new \ReflectionClass($entityClassName);
198
        $properties = array_merge($classMetaData->getFieldNames(), array_keys($classMetaData->associationMappings));
199
        foreach ($reflectionClass->getMethods() as $method) {
200
            if ($method->getName() === '__construct') {
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
201
                continue;
202
            }
203
204
            $usedProperties = $this->getUsedProperties($method, $properties);
205
            if (count($usedProperties) > 0) {
206
                if ($method->isPrivate()) {
207
                    throw new PrivateMethodShouldNotAccessPropertiesException(
208
                        $entityClassName,
209
                        $method->getName(),
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
210
                        $usedProperties
211
                    );
212
                }
213
214
                $return[] = $this->getPhpForMethod($method, $usedProperties);
215
            }
216
        }
217
218
        return $return;
219
    }
220
221
    /**
222
     * @param \ReflectionMethod $reflectionMethod
223
     * @param array $properties
224
     * @return string
225
     */
226
    protected function getPhpForMethod(\ReflectionMethod $reflectionMethod, array $properties)
227
    {
228
        if ($reflectionMethod->isPublic()) {
229
            $signature = 'public';
230
        } else {
231
            $signature = 'protected';
232
        }
233
        $signature .= ' function ' . $reflectionMethod->getName() . '(';
0 ignored issues
show
Bug introduced by
Consider using $reflectionMethod->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
234
        $parameters = array();
235
        foreach ($reflectionMethod->getParameters() as $parameter) {
236
            $parameters[] = $this->getPhpForParameter($parameter);
237
        }
238
        $signature .= implode(', ', $parameters) . ')';
239
240
        $method = $reflectionMethod->getName();
0 ignored issues
show
Bug introduced by
Consider using $reflectionMethod->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
241
242
        array_walk($properties, function(&$name) {
243
            $name = "'" . $name . "'";
244
        });
245
        $propertiesToAssert = implode(', ', $properties);
246
247
        $php = <<<PHP
248
    $signature
249
    {
250
        \$this->assertReadOnlyPropertiesAreLoaded(array($propertiesToAssert));
251
252
        return call_user_func_array(array('parent', '$method'), func_get_args());
253
    }
254
PHP;
255
256
        return $php;
257
    }
258
259
    /**
260
     * @param \ReflectionParameter $parameter
261
     * @return string
262
     */
263
    protected function getPhpForParameter(\ReflectionParameter $parameter)
264
    {
265
        $php = null;
266
        if ($parameter->getClass() instanceof \ReflectionClass) {
267
            $php .= '\\' . $parameter->getClass()->getName() . ' ';
0 ignored issues
show
Bug introduced by
Consider using $parameter->getClass()->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
268
        } elseif ($parameter->isCallable()) {
269
            $php .= 'callable ';
270
        }
271
272
        if ($parameter->isPassedByReference()) {
273
            $php .= '&';
274
        }
275
        $php .= '$' . $parameter->getName();
0 ignored issues
show
Bug introduced by
Consider using $parameter->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
276
277
        if ($parameter->isDefaultValueAvailable()) {
278
            if ($parameter->isDefaultValueConstant()) {
279
                $defaultValue = $parameter->getDefaultValueConstantName();
1 ignored issue
show
Bug introduced by
The method getDefaultValueConstantName() does not exist on ReflectionParameter. Did you maybe mean getDefaultValue()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
280
            } elseif ($parameter->getDefaultValue() === null) {
281
                $defaultValue = 'null';
282
            } elseif (is_string($parameter->getDefaultValue())) {
283
                $defaultValue = '\'' . $parameter->getDefaultValue() . '\'';
284
            } else {
285
                $defaultValue = $parameter->getDefaultValue();
286
            }
287
            $php .= ' = ' . $defaultValue;
288
        }
289
290
        return $php;
291
    }
292
}
293