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

Annotation/AnnotationDriver.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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) {
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(),
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
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) {
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) {
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();
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