Completed
Push — master ( 67a780...b51deb )
by Adam
02:58
created

Blameable::readExtendedMetadata()   D

Complexity

Conditions 29
Paths 53

Size

Total Lines 94
Code Lines 49

Duplication

Lines 6
Ratio 6.38 %

Code Coverage

Tests 44
CRAP Score 37.2768

Importance

Changes 3
Bugs 0 Features 1
Metric Value
c 3
b 0
f 1
dl 6
loc 94
ccs 44
cts 56
cp 0.7857
rs 4.4524
cc 29
eloc 49
nc 53
nop 2
crap 37.2768

How to fix   Long Method    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
 * Blameable.php
4
 *
5
 * @copyright      More in license.md
6
 * @license        http://www.ipublikuj.eu
7
 * @author         Adam Kadlec http://www.ipublikuj.eu
8
 * @package        iPublikuj:DoctrineBlameable!
9
 * @subpackage     Driver
10
 * @since          1.0.0
11
 *
12
 * @date           05.01.16
13
 */
14
15
namespace IPub\DoctrineBlameable\Mapping\Driver;
16
17
use Nette;
18
19
use Doctrine;
20
use Doctrine\Common;
21
use Doctrine\ORM;
22
23
use IPub;
24
use IPub\DoctrineBlameable;
25
use IPub\DoctrineBlameable\Exceptions;
26
use IPub\DoctrineBlameable\Mapping;
27
28
/**
29
 * Doctrine blameable annotation driver
30
 *
31
 * @package        iPublikuj:DoctrineBlameable!
32
 * @subpackage     Driver
33
 *
34
 * @author         Adam Kadlec <[email protected]>
35
 */
36
final class Blameable extends Nette\Object
37 1
{
38
	/**
39
	 * Define class name
40
	 */
41
	const CLASS_NAME = __CLASS__;
42
43
	/**
44
	 * Annotation field is blameable
45
	 */
46
	const EXTENSION_ANNOTATION = 'IPub\DoctrineBlameable\Mapping\Annotation\Blameable';
47
48
	/**
49
	 * @var Common\Persistence\ObjectManager
50
	 */
51
	private $objectManager;
52
53
	/**
54
	 * @var DoctrineBlameable\Configuration
55
	 */
56
	private $configuration;
57
58
	/**
59
	 * List of cached object configurations
60
	 *
61
	 * @var array
62
	 */
63
	private static $objectConfigurations = [];
64
65
	/**
66
	 * List of types which are valid for blame
67
	 *
68
	 * @var array
69
	 */
70
	private $validTypes = [
71
		'one',
72
		'string',
73
		'int',
74
	];
75
76
	/**
77
	 * @param DoctrineBlameable\Configuration $configuration
78
	 * @param Common\Persistence\ObjectManager $objectManager
79
	 */
80
	public function __construct(
81
		DoctrineBlameable\Configuration $configuration,
82
		Common\Persistence\ObjectManager $objectManager
83
	) {
84 1
		$this->objectManager = $objectManager;
85 1
		$this->configuration = $configuration;
86 1
	}
87
88
	/**
89
	 * @param ORM\Mapping\ClassMetadata $classMetadata
90
	 */
91
	public function loadMetadataForObjectClass(ORM\Mapping\ClassMetadata $classMetadata)
92
	{
93 1
		if ($classMetadata->isMappedSuperclass) {
94 1
			return; // Ignore mappedSuperclasses for now
95
		}
96
97
		// The annotation reader accepts a ReflectionClass, which can be
98
		// obtained from the $classMetadata
99 1
		$reflectionClass = $classMetadata->getReflectionClass();
100
101 1
		$config = [];
102
103 1
		$useObjectName = $classMetadata->getName();
104
105
		// Collect metadata from inherited classes
106 1
		if ($reflectionClass !== NULL) {
107 1
			foreach (array_reverse(class_parents($classMetadata->getName())) as $parentClass) {
108
				// Read only inherited mapped classes
109
				if ($this->objectManager->getMetadataFactory()->hasMetadataFor($parentClass)) {
110
					/** @var ORM\Mapping\ClassMetadata $parentClassMetadata */
111
					$parentClassMetadata = $this->objectManager->getClassMetadata($parentClass);
112
113
					$config = $this->readExtendedMetadata($parentClassMetadata, $config);
114
115
					$isBaseInheritanceLevel = !$parentClassMetadata->isInheritanceTypeNone()
116
						&& $parentClassMetadata->parentClasses !== []
117
						&& $config !== [];
118
119
					if ($isBaseInheritanceLevel === TRUE) {
120
						$useObjectName = $reflectionClass->getName();
121
					}
122
				}
123 1
			}
124
125 1
			$config = $this->readExtendedMetadata($classMetadata, $config);
126 1
		}
127
128 1
		if ($config !== []) {
129 1
			$config['useObjectClass'] = $useObjectName;
130 1
		}
131
132
		// Cache the metadata (even if it's empty)
133
		// Caching empty metadata will prevent re-parsing non-existent annotations
134 1
		$cacheId = self::getCacheId($classMetadata->getName());
135
136
		/** @var Common\Cache\Cache $cacheDriver */
137 1
		if ($cacheDriver = $this->objectManager->getMetadataFactory()->getCacheDriver()) {
138 1
			$cacheDriver->save($cacheId, $config, NULL);
139 1
		}
140
141 1
		self::$objectConfigurations[$classMetadata->getName()] = $config;
142 1
	}
143
144
	/**
145
	 * @param ORM\Mapping\ClassMetadata $metadata
146
	 * @param array $config
147
	 *
148
	 * @return array
149
	 *
150
	 * @throws Exceptions\InvalidMappingException
151
	 * @throws ORM\Mapping\MappingException
152
	 */
153
	private function readExtendedMetadata(ORM\Mapping\ClassMetadata $metadata, array $config)
154
	{
155 1
		$class = $metadata->getReflectionClass();
156
157
		// Create doctrine annotation reader
158 1
		$reader = $this->getDefaultAnnotationReader();
159
160
		// Property annotations
161 1
		foreach ($class->getProperties() as $property) {
162 1
			if ($metadata->isMappedSuperclass && $property->isPrivate() === FALSE ||
163 1
				$metadata->isInheritedField($property->getName()) ||
164 1
				isset($metadata->associationMappings[$property->getName()]['inherited'])
165 1
			) {
166
				continue;
167
			}
168
169
			/** @var Mapping\Annotation\Blameable $blameable */
170 1
			if ($blameable = $reader->getPropertyAnnotation($property, self::EXTENSION_ANNOTATION)) {
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $blameable is correct as $reader->getPropertyAnno...::EXTENSION_ANNOTATION) (which targets Doctrine\Common\Annotati...getPropertyAnnotation()) 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...
171 1
				$field = $property->getName();
172
173
				// No map field nor association
174 1
				if ($metadata->hasField($field) === FALSE && $metadata->hasAssociation($field) === FALSE && $this->configuration->useLazyAssociation() === FALSE) {
175 1
					if ($this->configuration->automapField) {
176 1
						if ($this->configuration->automapWithAssociation()) {
177
							$entityMap = [
178 1
								'targetEntity' => $this->configuration->userEntity,
179 1
								'fieldName'    => $field,
180
								'joinColumns'  => [
181
									[
182 1
										'onDelete' => 'SET NULL',
183
									]
184 1
								]
185 1
							];
186
187 1 View Code Duplication
							if (isset($blameable->association['column']) && $blameable->association['column'] !== NULL) {
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...
188
								$entityMap['joinColumns'][0]['name'] = $blameable->columnName;
189
							}
190
191 1 View Code Duplication
							if (isset($blameable->association['referencedColumn']) && $blameable->association['referencedColumn'] !== NULL) {
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...
192
								$entityMap['joinColumns'][0]['referencedColumnName'] = $blameable->referencedColumnName;
193
							}
194
195 1
							$metadata->mapManyToOne($entityMap);
196
197 1
						} else if ($this->configuration->automapWithField()) {
198 1
							$metadata->mapField([
199 1
								'fieldName' => $field,
200 1
								'type'      => 'string',
201 1
								'nullable'  => TRUE,
202 1
							]);
203
204 1
						} else {
205
							throw new Exceptions\InvalidMappingException("Unable to find blameable [{$field}] as mapped property in entity - {$metadata->getName()}");
206
						}
207
208 1
					} else {
209
						throw new Exceptions\InvalidMappingException("Unable to find blameable [{$field}] as mapped property in entity - {$metadata->getName()}");
210
					}
211 1
				}
212
213 1
				if ($metadata->hasField($field) && $this->isValidField($metadata, $field) === FALSE && $this->configuration->useLazyAssociation() === FALSE) {
214
					throw new Exceptions\InvalidMappingException("Field - [{$field}] type is not valid and must be 'string' or a one-to-many relation in class - {$metadata->getName()}");
215
216 1
				} else if ($metadata->hasAssociation($field) && $metadata->isSingleValuedAssociation($field) === FALSE && $this->configuration->useLazyAssociation() === FALSE) {
217
					throw new Exceptions\InvalidMappingException("Association - [{$field}] is not valid, it must be a one-to-many relation or a string field - {$metadata->getName()}");
218
				}
219
220
				// Check for valid events
221 1
				if (!in_array($blameable->on, ['update', 'create', 'change', 'delete'])) {
222
					throw new Exceptions\InvalidMappingException("Field - [{$field}] trigger 'on' is not one of [update, create, change] in class - {$metadata->getName()}");
223
				}
224
225 1
				if ($blameable->on === 'change') {
226 1
					if (!isset($blameable->field)) {
227
						throw new Exceptions\InvalidMappingException("Missing parameters on property - {$field}, field must be set on [change] trigger in class - {$metadata->getName()}");
228
					}
229
230 1
					if (is_array($blameable->field) && isset($blameable->value)) {
231
						throw new Exceptions\InvalidMappingException("Blameable extension does not support multiple value changeset detection yet.");
232
					}
233
234
					$field = [
235 1
						'field'        => $field,
236 1
						'trackedField' => $blameable->field,
237 1
						'value'        => is_array($blameable->value) ? $blameable->value : [$blameable->value],
238 1
					];
239 1
				}
240
241 1
				$config[$blameable->on][] = $field;
242 1
			}
243 1
		}
244
245 1
		return $config;
246
	}
247
248
	/**
249
	 * Get the configuration for specific object class
250
	 * if cache driver is present it scans it also
251
	 *
252
	 * @param string $class
253
	 *
254
	 * @return array
255
	 */
256
	public function getObjectConfigurations($class)
257
	{
258 1
		$config = [];
259
260 1
		if (isset(self::$objectConfigurations[$class])) {
261 1
			$config = self::$objectConfigurations[$class];
262
263 1
		} else {
264
			$metadataFactory = $this->objectManager->getMetadataFactory();
265
			/** @var Common\Cache\Cache $cacheDriver|NULL */
266
			$cacheDriver = $metadataFactory->getCacheDriver();
267
268
			if ($cacheDriver !== NULL) {
269
				$cacheId = self::getCacheId($class);
270
271
				if (($cached = $cacheDriver->fetch($cacheId)) !== FALSE) {
272
					self::$objectConfigurations[$class] = $cached;
273
					$config = $cached;
274
275
				} else {
276
					/** @var ORM\Mapping\ClassMetadata $classMetadata */
277
					$classMetadata = $metadataFactory->getMetadataFor($class);
278
279
					// Re-generate metadata on cache miss
280
					$this->loadMetadataForObjectClass($classMetadata);
281
282
					if (isset(self::$objectConfigurations[$class])) {
283
						$config = self::$objectConfigurations[$class];
284
					}
285
				}
286
287
				$objectClass = isset($config['useObjectClass']) ? $config['useObjectClass'] : $class;
288
289
				if ($objectClass !== $class) {
290
					$this->getObjectConfigurations($objectClass);
291
				}
292
			}
293
		}
294
295 1
		return $config;
296
	}
297
298
	/**
299
	 * Create default annotation reader for extensions
300
	 *
301
	 * @return Common\Annotations\AnnotationReader
302
	 */
303
	private function getDefaultAnnotationReader()
304
	{
305 1
		$reader = new Common\Annotations\AnnotationReader;
306
307 1
		Common\Annotations\AnnotationRegistry::registerAutoloadNamespace(
308
			'IPub\\DoctrineBlameable\\Mapping\\Annotation'
309 1
		);
310
311 1
		$reader = new Common\Annotations\CachedReader($reader, new Common\Cache\ArrayCache);
312
313 1
		return $reader;
314
	}
315
316
	/**
317
	 * Checks if $field type is valid
318
	 *
319
	 * @param object $meta
320
	 * @param string $field
321
	 *
322
	 * @return boolean
323
	 */
324
	private function isValidField($meta, $field)
325
	{
326 1
		$mapping = $meta->getFieldMapping($field);
327
328 1
		return $mapping && in_array($mapping['type'], $this->validTypes);
329
	}
330
331
	/**
332
	 * Get the cache id
333
	 *
334
	 * @param string $className
335
	 *
336
	 * @return string
337
	 */
338
	private static function getCacheId($className)
339
	{
340 1
		return $className . '\\$' . strtoupper(str_replace('\\', '_', __NAMESPACE__)) . '_CLASSMETADATA';
341
	}
342
343
}
344