Completed
Pull Request — master (#154)
by
unknown
05:50 queued 01:40
created

StaticProxyFactory   A

Complexity

Total Complexity 17

Size/Duplication

Total Lines 167
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
wmc 17
lcom 1
cbo 3
dl 0
loc 167
rs 10
c 0
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A generateProxyClasses() 0 19 3
A getProxy() 0 21 3
A makeInitializer() 0 21 1
A skippedFieldsFqns() 0 6 1
A transientFieldsFqns() 0 16 2
A identifierFieldFqns() 0 11 3
A propertyFqcn() 0 10 3
1
<?php
2
3
4
declare( strict_types=1 );
5
6
7
namespace Doctrine\ODM\CouchDB\Proxy;
8
9
10
use Closure;
11
use Doctrine\ODM\CouchDB\DocumentManager;
12
use Doctrine\ODM\CouchDB\Mapping\ClassMetadata;
13
use ProxyManager\Factory\LazyLoadingGhostFactory;
14
use ProxyManager\Proxy\GhostObjectInterface;
15
use ReflectionProperty;
16
17
/**
18
 * Static factory for proxy objects.
19
 *
20
 * @internal this class is to be used by ORM internals only
21
 */
22
final class StaticProxyFactory implements ProxyFactory {
23
	private const SKIPPED_PROPERTIES = 'skippedProperties';
24
25
	/** @var DocumentManager */
26
	private $dm;
27
28
	/** @var LazyLoadingGhostFactory */
29
	private $proxyFactory;
30
31
	/** @var Closure[] indexed by metadata class name */
32
	private $cachedInitializers = [];
33
34
	/** @var string[][][] indexed by metadata class name */
35
	private $cachedSkippedProperties = [];
36
37
	public function __construct(
38
		DocumentManager $dm,
39
		LazyLoadingGhostFactory $proxyFactory
40
	) {
41
		$this->dm           = $dm;
42
		$this->proxyFactory = $proxyFactory;
43
	}
44
45
	/**
46
	 * {@inheritdoc}
47
	 *
48
	 * @param ClassMetadata[] $classes
49
	 */
50
	public function generateProxyClasses( array $classes ): int {
51
		$concreteClasses = array_filter( $classes,
52
			static function ( ClassMetadata $metadata ): bool {
53
				return ! ( $metadata->isMappedSuperclass || $metadata->getReflectionClass()->isAbstract() );
54
			} );
55
		foreach ( $concreteClasses as $metadata ) {
56
			$this
57
				->proxyFactory
58
				->createProxy(
59
					$metadata->getName(),
60
					static function () {
61
						// empty closure, serves its purpose, for now
62
					},
63
					$this->skippedFieldsFqns( $metadata )
64
				);
65
		}
66
67
		return count( $concreteClasses );
68
	}
69
70
	/**
71
	 * {@inheritdoc}
72
	 *
73
	 * @param ClassMetadata $metadata
74
	 * @param string $identifier
75
	 *
76
	 * @return GhostObjectInterface
77
	 */
78
	public function getProxy( ClassMetadata $metadata, string $identifier ): GhostObjectInterface {
79
		$className     = $metadata->getName();
80
		$proxyInstance = $this
81
			->proxyFactory
82
			->createProxy(
83
				$metadata->getName(),
84
				$this->cachedInitializers[ $className ]
85
				?? $this->cachedInitializers[ $className ] = $this->makeInitializer( $metadata ),
86
				$this->cachedSkippedProperties[ $className ]
87
				?? $this->cachedSkippedProperties[ $className ] = [
88
					self::SKIPPED_PROPERTIES => $this->skippedFieldsFqns( $metadata ),
89
				]
90
			);
91
		$metadata->setIdentifierValue($proxyInstance, $identifier);
92
93
		if ( $this->dm->getClassMetadataFactory() && ! $this->dm->getClassMetadataFactory()->hasMetadataFor(get_class($proxyInstance))  ) {
94
			$this->dm->getClassMetadataFactory()->setMetadataFor( get_class( $proxyInstance ), $metadata );
95
		}
96
97
		return $proxyInstance;
98
	}
99
100
	/**
101
	 * @param ClassMetadata $metadata
102
	 *
103
	 * @return Closure
104
	 */
105
	private function makeInitializer( ClassMetadata $metadata ): Closure {
106
		$dm = $this->dm;
107
		return static function (
108
			GhostObjectInterface $ghostObject,
109
			string $method,
110
			// we don't care
111
			array $parameters,
112
			// we don't care
113
			& $initializer,
114
			array $properties // we currently do not use this
0 ignored issues
show
Unused Code introduced by
The parameter $properties is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
115
		) use (
116
			$metadata,
117
			$dm
118
		) : bool {
119
			$initializer         = null;
120
121
			$dm->refresh($ghostObject);
122
123
			return true;
124
		};
125
	}
126
127
	/**
128
	 * @param ClassMetadata $metadata
129
	 *
130
	 * @return string[]
131
	 */
132
	private function skippedFieldsFqns( ClassMetadata $metadata ): array {
133
		return array_merge(
134
			$this->identifierFieldFqns( $metadata ),
135
			$this->transientFieldsFqns( $metadata )
136
		);
137
	}
138
139
	/**
140
	 * @param ClassMetadata $metadata
141
	 *
142
	 * @return string[]
143
	 */
144
	private function transientFieldsFqns( ClassMetadata $metadata ): array {
0 ignored issues
show
Unused Code introduced by
The parameter $metadata is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
145
		return [];
146
147
		$transientFieldsFqns = [];
0 ignored issues
show
Unused Code introduced by
$transientFieldsFqns = array(); does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
148
		foreach ( $metadata->fieldMappings as $name => $property ) {
149
			// TODO : Transient fields don't have mapping
150
			/*
151
			if ( ! $property instanceof TransientMetadata ) {
152
				continue;
153
			}
154
			$transientFieldsFqns[] = $this->propertyFqcn( $metadata->getReflectionProperty( $name ) );
155
			*/
156
		}
157
158
		return $transientFieldsFqns;
159
	}
160
161
	/**
162
	 * @param ClassMetadata $metadata
163
	 *
164
	 * @return string[]
165
	 */
166
	private function identifierFieldFqns( ClassMetadata $metadata ): array {
167
		$idFieldFqcns = [];
168
		foreach ( $metadata->getIdentifierFieldNames() as $idField ) {
169
			if ( !$idField ) {
170
				continue;
171
			}
172
			$idFieldFqcns[] = $this->propertyFqcn( $metadata->getReflectionProperty( $idField ) );
173
		}
174
175
		return $idFieldFqcns;
176
	}
177
178
	private function propertyFqcn( ReflectionProperty $property ): string {
179
		if ( $property->isPrivate() ) {
180
			return "\0" . $property->getDeclaringClass()->getName() . "\0" . $property->getName();
0 ignored issues
show
introduced by
Consider using $property->class. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
181
		}
182
		if ( $property->isProtected() ) {
183
			return "\0*\0" . $property->getName();
184
		}
185
186
		return $property->getName();
187
	}
188
}