Completed
Pull Request — master (#433)
by Paul
11:06 queued 04:27
created

AnnotationDriver::loadBusinessProperties()   C

Complexity

Conditions 11
Paths 22

Size

Total Lines 48
Code Lines 27

Duplication

Lines 14
Ratio 29.17 %

Importance

Changes 2
Bugs 0 Features 2
Metric Value
c 2
b 0
f 2
dl 14
loc 48
rs 5.2653
cc 11
eloc 27
nc 22
nop 1

How to fix   Complexity   

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
namespace Victoire\Bundle\BusinessEntityBundle\Annotation;
4
5
use Doctrine\Common\Annotations\AnnotationException;
6
use Doctrine\Common\Annotations\Reader;
7
use Doctrine\ORM\Mapping\Column;
8
use Doctrine\ORM\Mapping\Driver\AnnotationDriver as DoctrineAnnotationDriver;
9
use Doctrine\ORM\Mapping\MappingException;
10
use Knp\DoctrineBehaviors\Model\Translatable\Translatable;
11
use Symfony\Component\Config\Definition\Exception\Exception;
12
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
13
use Symfony\Component\Validator\Constraints\NotBlank;
14
use Symfony\Component\Validator\Constraints\NotNull;
15
use Victoire\Bundle\BusinessEntityBundle\Entity\ReceiverProperty;
16
use Victoire\Bundle\BusinessEntityBundle\Event\BusinessEntityAnnotationEvent;
17
use Victoire\Bundle\BusinessEntityBundle\Helper\BusinessEntityHelper;
18
use Victoire\Bundle\CoreBundle\Annotations\BusinessEntity;
19
use Victoire\Bundle\CoreBundle\Annotations\BusinessProperty;
20
use Victoire\Bundle\CoreBundle\Annotations\ReceiverProperty as ReceiverPropertyAnnotation;
21
use Victoire\Bundle\WidgetBundle\Event\WidgetAnnotationEvent;
22
use Victoire\Bundle\WidgetBundle\Helper\WidgetHelper;
23
24
/**
25
 * Parse all files to get BusinessClasses.
26
 **/
27
class AnnotationDriver extends DoctrineAnnotationDriver
28
{
29
    public $reader;
30
    protected $eventDispatcher;
31
    protected $widgetHelper;
32
    protected $paths;
33
34
    /**
35
     * construct.
36
     *
37
     * @param Reader                   $reader
38
     * @param EventDispatcherInterface $eventDispatcher
39
     * @param WidgetHelper             $widgetHelper
40
     * @param array                    $paths           The paths where to search about Entities
41
     */
42
    public function __construct(Reader $reader, EventDispatcherInterface $eventDispatcher, $widgetHelper, $paths)
43
    {
44
        $this->reader = $reader;
45
        $this->eventDispatcher = $eventDispatcher;
46
        $this->widgetHelper = $widgetHelper;
47
        $this->paths = $paths;
48
    }
49
50
    /**
51
     * Get all class names.
52
     *
53
     * @return string[]
54
     */
55
    public function getAllClassNames()
56
    {
57
        if (!$this->paths) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->paths of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
58
            throw MappingException::pathRequired();
59
        }
60
        $classes = [];
61
        $includedFiles = [];
62
        foreach ($this->paths as $path) {
63
            if (!is_dir($path)) {
64
                throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path);
65
            }
66
            $iterator = new \RegexIterator(
67
                new \RecursiveIteratorIterator(
68
                    new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS),
69
                    \RecursiveIteratorIterator::LEAVES_ONLY
70
                ),
71
                '/^.+\/Entity\/.+\.php$/i',
72
                \RecursiveRegexIterator::GET_MATCH
73
            );
74
            foreach ($iterator as $file) {
75
                $sourceFile = realpath($file[0]);
76
                require_once $sourceFile;
77
                $includedFiles[] = $sourceFile;
78
            }
79
        }
80
        $declared = get_declared_classes();
81
        foreach ($declared as $className) {
82
            $rc = new \ReflectionClass($className);
83
            $sourceFile = $rc->getFileName();
84
            if (in_array($sourceFile, $includedFiles) && !$this->isTransient($className)) {
85
                $classes[] = $className;
86
            }
87
        }
88
89
        return $classes;
90
    }
91
92
    /**
93
     * Parse the given Class to find some annotations related to BusinessEntities.
94
     */
95
    public function parse(\ReflectionClass $class)
96
    {
97
        $classPath = dirname($class->getFileName());
98
        $inPaths = false;
99
100
        foreach ($this->paths as $key => $_path) {
101
            //Check the entity path is in watching paths
102
            if (strpos($classPath, realpath($_path)) === 0) {
103
                $inPaths = true;
104
            }
105
        }
106
107
        if ($inPaths) {
108
            $classAnnotations = $this->reader->getClassAnnotations($class);
109
110
            if (!empty($classAnnotations)) {
111
                foreach ($classAnnotations as $key => $annot) {
112
                    if (!is_numeric($key)) {
113
                        continue;
114
                    }
115
116
                    $classAnnotations[get_class($annot)] = $annot;
117
                }
118
            }
119
120
            // Evaluate Entity annotation
121
            if (isset($classAnnotations['Victoire\Bundle\CoreBundle\Annotations\BusinessEntity'])) {
122
                /** @var BusinessEntity $annotationObj */
123
                $annotationObj = $classAnnotations['Victoire\Bundle\CoreBundle\Annotations\BusinessEntity'];
124
                $businessEntity = BusinessEntityHelper::createBusinessEntity(
125
                    $class->getName(),
0 ignored issues
show
Bug introduced by
Consider using $class->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
126
                    $this->loadBusinessProperties($class)
127
                );
128
129
                $event = new BusinessEntityAnnotationEvent(
130
                    $businessEntity,
131
                    $annotationObj->getWidgets()
132
                );
133
134
                //do what you want (caching BusinessEntity...)
135
                $this->eventDispatcher->dispatch('victoire.business_entity_annotation_load', $event);
136
            }
137
138
            //check if the entity is a widget (extends (in depth) widget class)
139
            $parentClass = $class->getParentClass();
140
            $isWidget = false;
141
            while ($parentClass && ($parentClass = $parentClass->getParentClass()) && !$isWidget && $parentClass->name != null) {
142
                $isWidget = $parentClass->name === 'Victoire\\Bundle\\WidgetBundle\\Model\\Widget';
143
            }
144
145
            if ($isWidget) {
146
                if ($this->widgetHelper->isEnabled(new $class->name())) {
147
                    $event = new WidgetAnnotationEvent(
148
                        $this->widgetHelper->getWidgetName(new $class->name()),
149
                        $this->loadReceiverProperties($class)
150
                    );
151
152
                    //dispatch victoire.widget_annotation_load to save receiverProperties in cache
153
                    $this->eventDispatcher->dispatch('victoire.widget_annotation_load', $event);
154
                } else {
155
                    error_log(sprintf('Widget name not found for widget %s. Is this widget declared in AppKernel ?', $class->name));
156
                }
157
            }
158
        }
159
    }
160
161
    /**
162
     * load business properties from ReflectionClass.
163
     *
164
     * @return array
165
     **/
166
    protected function loadBusinessProperties(\ReflectionClass $class)
167
    {
168
        $businessProperties = [];
169
        $properties = $class->getProperties();
170
        $traits = $class->getTraits();
171
        $className = $class->getName();
0 ignored issues
show
Bug introduced by
Consider using $class->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
172
        // if the class is translatable, then parse annotations on it's translation class
173
        if (array_key_exists(Translatable::class, $traits)) {
174
            $translation = new \ReflectionClass($className::getTranslationEntityClass());
175
            $translationProperties = $translation->getProperties();
176
            $properties = array_merge($properties, $translationProperties);
177
178
        }
179
180 View Code Duplication
        foreach ($properties as $property) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
181
            $annotations = $this->reader->getPropertyAnnotations($property);
182
            foreach ($annotations as $key => $annotationObj) {
183
                if ($annotationObj instanceof BusinessProperty && !in_array($class, $businessProperties)) {
184
                    if (!$annotations[$key]->getTypes()) {
185
                        $message = $class->name.':$'.$property->name.'" field';
186
                        throw AnnotationException::requiredError('type', 'BusinessProperty annotation', $message, 'array or string');
187
                    }
188
                    foreach ($annotations[$key]->getTypes() as $type) {
189
                        $businessProperties[$type][] = $property->name;
190
                    }
191
                }
192
            }
193
        }
194
        // we load business properties of parents recursively
195
        // because they are defined by an annotation not by the property type(private, protected, public)
196
        $parentClass = $class->getParentClass();
197
        if ($parentClass) {
198
            //load parent properties recursively
199
            $parentProperties = $this->loadBusinessProperties(new \ReflectionClass($parentClass->getName()));
200
            foreach ($parentProperties as $key => $parentProperty) {
201
                if (array_key_exists($key, $businessProperties)) {
202
                    //if parent and current have a same business property type we merge the properties and remove
203
                    //duplicates if properties are the same;
204
                    $businessProperties[$key] = array_unique(array_merge($parentProperty, $businessProperties[$key]));
205
                } else {
206
                    //else we had a business property type for the parent properties
207
                    $businessProperties[$key] = $parentProperty;
208
                }
209
            }
210
        }
211
212
        return $businessProperties;
213
    }
214
215
    /**
216
     * Load receiver properties and NotBlank constraints from ReflectionClass.
217
     *
218
     * @param \ReflectionClass $class
219
     *
220
     * @throws AnnotationException
221
     *
222
     * @return array
223
     */
224
    protected function loadReceiverProperties(\ReflectionClass $class)
225
    {
226
        $receiverPropertiesTypes = [];
227
        $properties = $class->getProperties();
228
229
        //Store receiver properties
230
        foreach ($properties as $property) {
231
            $annotations = $this->reader->getPropertyAnnotations($property);
232 View Code Duplication
            foreach ($annotations as $key => $annotationObj) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
233
                if ($annotationObj instanceof ReceiverPropertyAnnotation && !in_array($class, $receiverPropertiesTypes)) {
234
                    if (!$annotations[$key]->getTypes()) {
235
                        $message = $class->name.':$'.$property->name.'" field';
236
                        throw AnnotationException::requiredError('type', 'ReceiverProperty annotation', $message, 'array or string');
237
                    }
238
                    foreach ($annotations[$key]->getTypes() as $type) {
239
                        $receiverProperty = new ReceiverProperty();
240
                        $receiverProperty->setFieldName($property->name);
241
                        $receiverPropertiesTypes[$type][] = $receiverProperty;
242
                    }
243
                }
244
            }
245
        }
246
247
        //Set receiver properties as required if necessary
248
        foreach ($receiverPropertiesTypes as $type => $receiverProperties) {
249
            /* @var ReceiverProperty[] $receiverProperties */
250
            foreach ($receiverProperties as $receiverProperty) {
251
                $receiverPropertyName = $receiverProperty->getFieldName();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $receiverPropertyName is correct as $receiverProperty->getFieldName() (which targets Victoire\Bundle\Business...roperty::getFieldName()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
252
                $refProperty = $class->getProperty($receiverPropertyName);
253
                $annotations = $this->reader->getPropertyAnnotations($refProperty);
254
255
                foreach ($annotations as $key => $annotationObj) {
256
                    if ($annotationObj instanceof Column && $annotationObj->nullable === false) {
257
                        throw new Exception(sprintf(
258
                                'Property "%s" in class "%s" has a @ReceiverProperty annotation and by consequence must have "nullable=true" for ORM\Column annotation',
259
                                $refProperty->name,
260
                                $refProperty->class
261
                            ));
262
                    } elseif ($annotationObj instanceof NotBlank) {
263
                        throw new Exception(sprintf(
264
                                'Property "%s" in class "%s" has a @ReceiverProperty annotation and by consequence can not use NotBlank annotation',
265
                                $refProperty->name,
266
                                $refProperty->class
267
                            ));
268
                    } elseif ($annotationObj instanceof NotNull) {
269
                        throw new Exception(sprintf(
270
                                'Property "%s" in class "%s" has a @ReceiverProperty annotation and by consequence can not use NotNull annotation',
271
                                $refProperty->name,
272
                                $refProperty->class
273
                            ));
274
                    } elseif ($annotationObj instanceof ReceiverPropertyAnnotation && $annotationObj->isRequired()) {
275
                        $receiverProperty->setRequired(true);
276
                    }
277
                }
278
            }
279
        }
280
281
        return $receiverPropertiesTypes;
282
    }
283
}
284