Completed
Push — master ( 306913...a9d592 )
by Jelle
04:33
created

EntityManager::reverseMapProperties()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 8
cts 8
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 8
nc 5
nop 2
crap 4
1
<?php
2
/**
3
 * @file
4
 * Contains \TheSportsDb\Entity\EntityManager.
5
 */
6
7
namespace TheSportsDb\Entity;
8
9
use FastNorth\PropertyMapper\Map;
10
use FastNorth\PropertyMapper\MapperInterface;
11
use TheSportsDb\Entity\Factory\FactoryContainerInterface;
12
use TheSportsDb\Entity\Repository\RepositoryContainerInterface;
13
use TheSportsDb\PropertyMapper\Transformer\Callback;
14
15
/**
16
 * Default implementation for entity managers.
17
 *
18
 * @author Jelle Sebreghts
19
 */
20
class EntityManager implements EntityManagerInterface {
21
22
  /**
23
   * The factory container.
24
   *
25
   * @var \TheSportsDb\Entity\Factory\FactoryContainerInterface
26
   */
27
  protected $factoryContainer;
28
29
  /**
30
   * The repository container.
31
   *
32
   * @var \TheSportsDb\Entity\Repository\RepositoryContainerInterface
33
   */
34
  protected $repositoryContainer;
35
36
  /**
37
   * Map entity types to classes.
38
   *
39
   * @var array
40
   */
41
  protected $classes = array();
42
43
  /**
44
   * Property map definitions.
45
   *
46
   * @var array
47
   */
48
  protected $propertyMapDefinitions = array();
49
50
  /**
51
   * Property map definitions.
52
   *
53
   * @var array
54
   */
55
  protected $propertyMaps = array();
56
57
  /**
58
   * The property mapper.
59
   *
60
   * @var \FastNorth\PropertyMapper\MapperInterface
61
   */
62
  protected $propertyMapper;
63
64
65
  /**
66
   * Placeholder for empty properties.
67
   *
68
   * @var string
69
   */
70
  const EMPTYPROPERTYPLACEHOLDER = '__EMPTY_PROPERTY_PLACEHOLDER__';
71
72
73
  /**
74
   * {@inheritdoc}
75
   */
76
  public function __construct(MapperInterface $propertyMapper, FactoryContainerInterface $factoryContainer = NULL, RepositoryContainerInterface $repositoryContainer = NULL) {
77
    if ($factoryContainer instanceof FactoryContainerInterface) {
78
      $this->factoryContainer = $factoryContainer;
79
    }
80
    if ($repositoryContainer instanceof RepositoryContainerInterface) {
81
      $this->repositoryContainer = $repositoryContainer;
82
    }
83
    $this->propertyMapper = $propertyMapper;
84
  }
85
86
  /**
87
   * {@inheritdoc}
88
   */
89 1
  public function setFactoryContainer(FactoryContainerInterface $factoryContainer) {
90 1
    if ($this->factoryContainer instanceof FactoryContainerInterface) {
91 1
      throw new \Exception('Factory container already set.');
92
    }
93 1
    $this->factoryContainer = $factoryContainer;
94 1
  }
95
96
  /**
97
   * {@inheritdoc}
98
   */
99 1
  public function setRepositoryContainer(RepositoryContainerInterface $repositoryContainer) {
100 1
    if ($this->repositoryContainer instanceof RepositoryContainerInterface) {
101 1
      throw new \Exception('Repository container already set.');
102
    }
103 1
    $this->repositoryContainer = $repositoryContainer;
104 1
  }
105
106
107
  /**
108
   * {@inheritdoc}
109
   */
110 1
  public function repository($entityType) {
111 1
    if ($this->repositoryContainer instanceof RepositoryContainerInterface) {
112
      return $this->repositoryContainer->getRepository($entityType);
113
    }
114 1
    throw new \Exception('No repository container set.');
115
  }
116
117
  /**
118
   * {@inheritdoc}
119
   */
120 1
  public function factory($entityType) {
121 1
    if ($this->factoryContainer instanceof FactoryContainerInterface) {
122
      return $this->factoryContainer->getFactory($entityType);
123
    }
124 1
    throw new \Exception('No factory container set.');
125
  }
126
127
  /**
128
   * {@inheritdoc}
129
   */
130 1
  public function registerClass($entityType, $realClass = NULL, $proxyClass = NULL) {
131 1
    if (is_null($realClass)) {
132 1
      $realClass = (new \ReflectionClass(static::class))->getNamespaceName() . '\\' . ucfirst($entityType);
133
    }
134 1
    if (!class_exists($realClass)) {
135 1
      throw new \Exception('Class ' . $realClass . ' not found.');
136
    }
137 1
    if (is_null($proxyClass)) {
138 1
      $proxyClass = (new \ReflectionClass($realClass))->getNamespaceName() . '\\Proxy\\' . ucfirst($entityType) . 'Proxy';
139
    }
140 1
    if (!class_exists($proxyClass)) {
141
      throw new \Exception('Class ' . $proxyClass . 'not found.');
142
    }
143 1
    $this->classes[$entityType] = array(
144 1
      'real' => $realClass,
145 1
      'proxy' => $proxyClass
146
    );
147 1
    return $this->classes[$entityType];
148
  }
149
150
  /**
151
   * {@inheritdoc}
152
   */
153 1
  public function getPropertyMapDefinition($entityType) {
154 1
    if (!isset($this->propertyMapDefinitions[$entityType])) {
155 1
      $propertyMapDefinition = new \ReflectionMethod($this->getClass($entityType), 'getPropertyMapDefinition');
156 1
      $this->propertyMapDefinitions[$entityType] = $propertyMapDefinition->invoke(NULL);
157
    }
158 1
    return $this->propertyMapDefinitions[$entityType];
159
  }
160
161
  /**
162
   * {@inheritdoc}
163
   */
164 1
  public function getClass($entityType, $type = 'real') {
165 1
    if (!isset($this->classes[$entityType][$type])) {
166
      throw new \Exception(ucfirst($type) . ' class for ' . $entityType . ' not registered.');
167
    }
168 1
    return $this->classes[$entityType][$type];
169
  }
170
171
  /**
172
   * {@inheritdoc}
173
   */
174 1
  public function mapProperties(\stdClass $values, $entityType) {
175 1
    $mapped = new \stdClass();
176 1
    foreach ($this->getPropertyMapDefinition($entityType)->getPropertyMaps() as $map) {
177
      // The property on the destination must exist or it will not map.
178 1
      $mapped->{$map->getDestination()->getName()} = NULL;
179 1
      if (!isset($values->{$map->getSource()->getName()})) {
180
        // If a source property is not set, we must unset it on the destination
181
        // later on, so give it a value we can recognize.
182 1
        $values->{$map->getSource()->getName()} = static::EMPTYPROPERTYPLACEHOLDER;
183
      }
184
    }
185 1
    return $this->sanitizeObject($this->propertyMapper->process($values, $mapped, $this->getPropertyMap($entityType)));
186
  }
187
188
189
  /**
190
   * {@inheritdoc}
191
   */
192 1
  public function reverseMapProperties(\stdClass $values, $entityType) {
193 1
    $reversed = new \stdClass();
194 1
    foreach ($this->getPropertyMapDefinition($entityType)->getPropertyMaps() as $map) {
195 1
      if (!isset($reversed->{$map->getSource()->getName()})) {
196 1
        $reversed->{$map->getSource()->getName()} = static::EMPTYPROPERTYPLACEHOLDER;
197
      }
198 1
      if (!isset($values->{$map->getDestination()->getName()})) {
199 1
        $values->{$map->getDestination()->getName()} = static::EMPTYPROPERTYPLACEHOLDER;
200
      }
201
    }
202 1
    return $this->sanitizeObject($this->propertyMapper->reverse($reversed, $values, $this->getPropertyMap($entityType)));
203
  }
204
205
  /**
206
   * {@inheritdoc}
207
   */
208 1
  public function isFullObject(\stdClass $object, $entityType) {
209 1
    $reflection = new \ReflectionClass($this->getClass($entityType));
210 1
    $defaultProperties = $reflection->getDefaultProperties();
211
    $properties = array_flip(array_filter(array_keys($defaultProperties), function($prop) use ($reflection) {
212
      // Filter out static properties.
213 1
      return !$reflection->getProperty($prop)->isStatic();
214 1
    }));
215 1
    return count(array_intersect_key($properties, (array) $object)) === count($properties);
216
  }
217
218
  /**
219
   * Initializes the property map.
220
   *
221
   * @param string $entityType
222
   *   The entity type to initialize the property map for.
223
   *
224
   * @return void
225
   */
226
  protected function initPropertyMap($entityType) {
227
    $this->propertyMaps[$entityType] = new Map();
228
    $entityManager = $this;
229
    foreach ($this->getPropertyMapDefinition($entityType)->getPropertyMaps() as $map) {
230
      $args = [
231
        $map->getSource()->getName(),
232
        $map->getDestination()->getName(),
233
      ];
234
      if ($map->getTransform()) {
235
        $transform = $map->getTransform();
236
        $reverse = $map->getReverse();
237
        $args[] = new Callback(
238
239
          /**
240
           * @param string $value
241
           */
242
          function($value, $context) use ($entityManager, $transform) {
243
            if ($entityManager->isEmptyValue($value)) {
244
              return $value;
245
            }
246
            return call_user_func_array($transform, array($value, $context, $entityManager));
247
          },
248
249
          /**
250
           * @param string $value
251
           */
252
          function($value, $context) use ($entityManager, $reverse) {
253
            if ($entityManager->isEmptyValue($value)) {
254
              return $value;
255
            }
256
            return call_user_func_array($reverse, array($value, $context, $entityManager));
257
          }
258
        );
259
      }
260
      call_user_func_array(array($this->propertyMaps[$entityType], 'map'), $args);
261
    }
262
  }
263
264
  /**
265
   * Gets the property map.
266
   *
267
   * @param string $entityType
268
   *   The entity type to get the map for.
269
   *
270
   * @return \FastNorth\PropertyMapper\MapInterface
271
   *   The property map.
272
   */
273
  protected function getPropertyMap($entityType) {
274
    if (!isset($this->propertyMaps[$entityType])) {
275
      $this->initPropertyMap($entityType);
276
    }
277
    return $this->propertyMaps[$entityType];
278
  }
279
280
  /**
281
   * Sanitize empty values from an object.
282
   *
283
   * @param \stdClass $object
284
   *   The object to sanitize.
285
   *
286
   * @return \stdClass
287
   *   The sanitized object.
288
   */
289 1
  protected function sanitizeObject(\stdClass $object) {
290 1
    $arr = (array) $object;
291 1
    foreach ($arr as $prop => $val) {
292 1
      if ($this->isEmptyValue($val)) {
293 1
        unset($arr[$prop]);
294
      }
295
    }
296 1
    return (object) $arr;
297
  }
298
299
  /**
300
   * {@inheritdoc}
301
   */
302 1
  public function isEmptyValue($value) {
303 1
    return $value === static::EMPTYPROPERTYPLACEHOLDER;
304
  }
305
306
  /**
307
   * {@inheritdoc}
308
   */
309
  public function sanitizeValues(\stdClass &$values, $entityType) {
310
    foreach ($this->getPropertyMapDefinition($entityType)->getPropertyMaps() as $map) {
311
      $map->getDestination()->sanitizeProperty($values, $this->factoryContainer);
312
    }
313
  }
314
}
315