Completed
Push — master ( a02710...7ee086 )
by Jelle
09:44
created

EntityManager::sanitizeProperty()   B

Complexity

Conditions 7
Paths 6

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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