Completed
Push — master ( 5d51b4...87fdd9 )
by Paul
10s
created

AnnotationDriver::loadBusinessProperties()   C

Complexity

Conditions 11
Paths 22

Size

Total Lines 47
Code Lines 27

Duplication

Lines 14
Ratio 29.79 %

Importance

Changes 3
Bugs 1 Features 2
Metric Value
c 3
b 1
f 2
dl 14
loc 47
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 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...
180
            $annotations = $this->reader->getPropertyAnnotations($property);
181
            foreach ($annotations as $key => $annotationObj) {
182
                if ($annotationObj instanceof BusinessProperty && !in_array($class, $businessProperties)) {
183
                    if (!$annotations[$key]->getTypes()) {
184
                        $message = $class->name.':$'.$property->name.'" field';
185
                        throw AnnotationException::requiredError('type', 'BusinessProperty annotation', $message, 'array or string');
186
                    }
187
                    foreach ($annotations[$key]->getTypes() as $type) {
188
                        $businessProperties[$type][] = $property->name;
189
                    }
190
                }
191
            }
192
        }
193
        // we load business properties of parents recursively
194
        // because they are defined by an annotation not by the property type(private, protected, public)
195
        $parentClass = $class->getParentClass();
196
        if ($parentClass) {
197
            //load parent properties recursively
198
            $parentProperties = $this->loadBusinessProperties(new \ReflectionClass($parentClass->getName()));
199
            foreach ($parentProperties as $key => $parentProperty) {
200
                if (array_key_exists($key, $businessProperties)) {
201
                    //if parent and current have a same business property type we merge the properties and remove
202
                    //duplicates if properties are the same;
203
                    $businessProperties[$key] = array_unique(array_merge($parentProperty, $businessProperties[$key]));
204
                } else {
205
                    //else we had a business property type for the parent properties
206
                    $businessProperties[$key] = $parentProperty;
207
                }
208
            }
209
        }
210
211
        return $businessProperties;
212
    }
213
214
    /**
215
     * Load receiver properties and NotBlank constraints from ReflectionClass.
216
     *
217
     * @param \ReflectionClass $class
218
     *
219
     * @throws AnnotationException
220
     *
221
     * @return array
222
     */
223
    protected function loadReceiverProperties(\ReflectionClass $class)
224
    {
225
        $receiverPropertiesTypes = [];
226
        $properties = $class->getProperties();
227
228
        //Store receiver properties
229
        foreach ($properties as $property) {
230
            $annotations = $this->reader->getPropertyAnnotations($property);
231 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...
232
                if ($annotationObj instanceof ReceiverPropertyAnnotation && !in_array($class, $receiverPropertiesTypes)) {
233
                    if (!$annotations[$key]->getTypes()) {
234
                        $message = $class->name.':$'.$property->name.'" field';
235
                        throw AnnotationException::requiredError('type', 'ReceiverProperty annotation', $message, 'array or string');
236
                    }
237
                    foreach ($annotations[$key]->getTypes() as $type) {
238
                        $receiverProperty = new ReceiverProperty();
239
                        $receiverProperty->setFieldName($property->name);
240
                        $receiverPropertiesTypes[$type][] = $receiverProperty;
241
                    }
242
                }
243
            }
244
        }
245
246
        //Set receiver properties as required if necessary
247
        foreach ($receiverPropertiesTypes as $type => $receiverProperties) {
248
            /* @var ReceiverProperty[] $receiverProperties */
249
            foreach ($receiverProperties as $receiverProperty) {
250
                $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...
251
                $refProperty = $class->getProperty($receiverPropertyName);
252
                $annotations = $this->reader->getPropertyAnnotations($refProperty);
253
254
                foreach ($annotations as $key => $annotationObj) {
255
                    if ($annotationObj instanceof Column && $annotationObj->nullable === false) {
256
                        throw new Exception(sprintf(
257
                                'Property "%s" in class "%s" has a @ReceiverProperty annotation and by consequence must have "nullable=true" for ORM\Column annotation',
258
                                $refProperty->name,
259
                                $refProperty->class
260
                            ));
261
                    } elseif ($annotationObj instanceof NotBlank) {
262
                        throw new Exception(sprintf(
263
                                'Property "%s" in class "%s" has a @ReceiverProperty annotation and by consequence can not use NotBlank annotation',
264
                                $refProperty->name,
265
                                $refProperty->class
266
                            ));
267
                    } elseif ($annotationObj instanceof NotNull) {
268
                        throw new Exception(sprintf(
269
                                'Property "%s" in class "%s" has a @ReceiverProperty annotation and by consequence can not use NotNull annotation',
270
                                $refProperty->name,
271
                                $refProperty->class
272
                            ));
273
                    } elseif ($annotationObj instanceof ReceiverPropertyAnnotation && $annotationObj->isRequired()) {
274
                        $receiverProperty->setRequired(true);
275
                    }
276
                }
277
            }
278
        }
279
280
        return $receiverPropertiesTypes;
281
    }
282
}
283