bankiru /
doctrine-api-client
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 Bankiru\Api\Doctrine\Persister; |
||
| 4 | |||
| 5 | use Bankiru\Api\Doctrine\ApiEntityManager; |
||
| 6 | use Bankiru\Api\Doctrine\Mapping\ApiMetadata; |
||
| 7 | use Bankiru\Api\Doctrine\Mapping\EntityMetadata; |
||
| 8 | use Bankiru\Api\Doctrine\Proxy\ApiCollection; |
||
| 9 | use Bankiru\Api\Doctrine\Rpc\CrudsApiInterface; |
||
| 10 | use Bankiru\Api\Doctrine\Rpc\SearchArgumentsTransformer; |
||
| 11 | use Doctrine\Common\Collections\AbstractLazyCollection; |
||
| 12 | |||
| 13 | /** @internal */ |
||
| 14 | class ApiPersister implements EntityPersister |
||
| 15 | { |
||
| 16 | /** @var SearchArgumentsTransformer */ |
||
| 17 | private $transformer; |
||
| 18 | /** @var EntityMetadata */ |
||
| 19 | private $metadata; |
||
| 20 | /** @var ApiEntityManager */ |
||
| 21 | private $manager; |
||
| 22 | /** @var CrudsApiInterface */ |
||
| 23 | private $api; |
||
| 24 | /** @var array */ |
||
| 25 | private $pendingInserts = []; |
||
| 26 | |||
| 27 | /** |
||
| 28 | * ApiPersister constructor. |
||
| 29 | * |
||
| 30 | * @param ApiEntityManager $manager |
||
| 31 | * @param CrudsApiInterface $api |
||
| 32 | */ |
||
| 33 | 19 | public function __construct(ApiEntityManager $manager, CrudsApiInterface $api) |
|
| 34 | { |
||
| 35 | 19 | $this->manager = $manager; |
|
| 36 | 19 | $this->metadata = $api->getMetadata(); |
|
| 37 | 19 | $this->api = $api; |
|
| 38 | 19 | $this->transformer = new SearchArgumentsTransformer($this->metadata, $this->manager); |
|
| 39 | 19 | } |
|
| 40 | |||
| 41 | /** {@inheritdoc} */ |
||
| 42 | public function getClassMetadata() |
||
| 43 | { |
||
| 44 | return $this->metadata; |
||
| 45 | } |
||
| 46 | |||
| 47 | /** {@inheritdoc} */ |
||
| 48 | 5 | public function getCrudsApi() |
|
| 49 | { |
||
| 50 | 5 | return $this->api; |
|
| 51 | } |
||
| 52 | |||
| 53 | /** {@inheritdoc} */ |
||
| 54 | 1 | public function update($entity) |
|
| 55 | { |
||
| 56 | 1 | $patch = $this->prepareUpdateData($entity); |
|
| 57 | 1 | $data = $this->convertEntityToData($entity); |
|
| 58 | |||
| 59 | 1 | $this->api->patch( |
|
| 60 | 1 | $this->transformer->transformCriteria($this->metadata->getIdentifierValues($entity)), |
|
| 61 | 1 | $patch, |
|
| 62 | $data |
||
| 63 | 1 | ); |
|
| 64 | 1 | } |
|
| 65 | |||
| 66 | /** {@inheritdoc} */ |
||
| 67 | 1 | public function delete($entity) |
|
| 68 | { |
||
| 69 | 1 | return $this->api->remove($this->transformer->transformCriteria($this->metadata->getIdentifierValues($entity))); |
|
| 70 | } |
||
| 71 | |||
| 72 | /** {@inheritdoc} */ |
||
| 73 | 1 | public function count($criteria = []) |
|
| 74 | { |
||
| 75 | 1 | return $this->api->count($this->transformer->transformCriteria($criteria)); |
|
| 76 | } |
||
| 77 | |||
| 78 | /** {@inheritdoc} */ |
||
| 79 | 3 | public function loadAll(array $criteria = [], array $orderBy = null, $limit = null, $offset = null) |
|
| 80 | { |
||
| 81 | 3 | $objects = $this->api->search( |
|
| 82 | 3 | $this->transformer->transformCriteria($criteria), |
|
| 83 | 3 | $this->transformer->transformOrder($orderBy), |
|
| 84 | 3 | $limit, |
|
| 85 | $offset |
||
| 86 | 3 | ); |
|
| 87 | |||
| 88 | 3 | $entities = []; |
|
| 89 | 3 | foreach ($objects as $object) { |
|
| 90 | 3 | $entities[] = $this->manager->getUnitOfWork()->getOrCreateEntity($this->metadata->getName(), $object); |
|
| 91 | 3 | } |
|
| 92 | |||
| 93 | 3 | return $entities; |
|
| 94 | } |
||
| 95 | |||
| 96 | /** {@inheritdoc} */ |
||
| 97 | public function loadOneToOneEntity(array $assoc, $sourceEntity, array $identifier = []) |
||
| 98 | { |
||
| 99 | if (false !== ($foundEntity = $this->manager->getUnitOfWork()->tryGetById($identifier, $assoc['target']))) { |
||
| 100 | return $foundEntity; |
||
| 101 | } |
||
| 102 | |||
| 103 | // Get identifiers from entity if the entity is not the owning side |
||
| 104 | if (!$assoc['isOwningSide']) { |
||
| 105 | $identifier = $this->metadata->getIdentifierValues($sourceEntity); |
||
| 106 | } |
||
| 107 | |||
| 108 | return $this->loadById($identifier); |
||
| 109 | } |
||
| 110 | |||
| 111 | /** {@inheritdoc} */ |
||
| 112 | 11 | public function loadById(array $identifiers, $entity = null) |
|
| 113 | { |
||
| 114 | 11 | $body = $this->api->find($this->transformer->transformCriteria($identifiers)); |
|
| 115 | |||
| 116 | 11 | if (null === $body) { |
|
| 117 | return null; |
||
| 118 | } |
||
| 119 | |||
| 120 | 11 | return $this->manager->getUnitOfWork()->getOrCreateEntity($this->metadata->getName(), $body); |
|
| 121 | } |
||
| 122 | |||
| 123 | /** {@inheritdoc} */ |
||
| 124 | public function refresh(array $id, $entity) |
||
| 125 | { |
||
| 126 | $this->loadById($id, $entity); |
||
| 127 | } |
||
| 128 | |||
| 129 | /** {@inheritdoc} */ |
||
| 130 | 2 | public function loadOneToManyCollection(array $assoc, $sourceEntity, AbstractLazyCollection $collection) |
|
| 131 | { |
||
| 132 | $criteria = [ |
||
| 133 | 2 | $assoc['mappedBy'] => $sourceEntity, |
|
| 134 | 2 | ]; |
|
| 135 | |||
| 136 | 2 | $orderBy = isset($assoc['orderBy']) ? $assoc['orderBy'] : []; |
|
| 137 | |||
| 138 | 2 | $source = $this->api->search( |
|
| 139 | 2 | $this->transformer->transformCriteria($criteria), |
|
| 140 | 2 | $this->transformer->transformOrder($orderBy) |
|
| 141 | 2 | ); |
|
| 142 | |||
| 143 | 2 | $target = $this->manager->getClassMetadata($assoc['target']); |
|
| 144 | |||
| 145 | 2 | foreach ($source as $object) { |
|
| 146 | 2 | $entity = $this->manager->getUnitOfWork()->getOrCreateEntity($target->getName(), $object); |
|
| 147 | 2 | if (isset($assoc['orderBy'])) { |
|
| 148 | $index = $target->getReflectionProperty($assoc['orderBy'])->getValue($entity); |
||
| 149 | $collection->set($index, $entity); |
||
| 150 | } else { |
||
| 151 | 2 | $collection->add($entity); |
|
| 152 | } |
||
| 153 | |||
| 154 | 2 | $target->getReflectionProperty($assoc['mappedBy'])->setValue($entity, $sourceEntity); |
|
| 155 | 2 | } |
|
| 156 | |||
| 157 | 2 | return $collection; |
|
| 158 | } |
||
| 159 | |||
| 160 | /** {@inheritdoc} */ |
||
| 161 | 10 | public function getOneToManyCollection(array $assoc, $sourceEntity, $limit = null, $offset = null) |
|
| 162 | { |
||
| 163 | 10 | $targetClass = $assoc['target']; |
|
| 164 | /** @var EntityMetadata $targetMetadata */ |
||
| 165 | 10 | $targetMetadata = $this->manager->getClassMetadata($targetClass); |
|
| 166 | |||
| 167 | 10 | if ($this->metadata->isIdentifierComposite) { |
|
| 168 | throw new \BadMethodCallException(__METHOD__ . ' on composite reference is not supported'); |
||
| 169 | } |
||
| 170 | |||
| 171 | 10 | $apiCollection = new ApiCollection($this->manager, $targetMetadata); |
|
| 172 | 10 | $apiCollection->setOwner($sourceEntity, $assoc); |
|
| 173 | 10 | $apiCollection->setInitialized(false); |
|
| 174 | |||
| 175 | 10 | return $apiCollection; |
|
| 176 | } |
||
| 177 | |||
| 178 | /** {@inheritdoc} */ |
||
| 179 | 5 | public function getToOneEntity(array $mapping, $sourceEntity, array $identifiers) |
|
| 180 | { |
||
| 181 | 5 | $metadata = $this->manager->getClassMetadata(get_class($sourceEntity)); |
|
| 182 | |||
| 183 | 5 | if (!$mapping['isOwningSide']) { |
|
| 184 | $identifiers = $metadata->getIdentifierValues($sourceEntity); |
||
| 185 | 1 | } |
|
| 186 | |||
| 187 | 5 | return $this->manager->getReference($mapping['target'], $identifiers); |
|
| 188 | } |
||
| 189 | |||
| 190 | 5 | public function pushNewEntity($entity) |
|
| 191 | { |
||
| 192 | 5 | $this->pendingInserts[] = $entity; |
|
| 193 | 5 | } |
|
| 194 | |||
| 195 | 5 | public function flushNewEntities() |
|
| 196 | { |
||
| 197 | 5 | $result = []; |
|
| 198 | 5 | foreach ($this->pendingInserts as $entity) { |
|
| 199 | 5 | $result[] = [ |
|
| 200 | 5 | 'generatedId' => $this->getCrudsApi()->create($this->convertEntityToData($entity)), |
|
| 201 | 5 | 'entity' => $entity, |
|
| 202 | ]; |
||
| 203 | 5 | } |
|
| 204 | |||
| 205 | 5 | $this->pendingInserts = []; |
|
| 206 | |||
| 207 | 5 | if ($this->metadata->isIdentifierNatural()) { |
|
| 208 | return []; |
||
| 209 | } |
||
| 210 | |||
| 211 | 5 | return $result; |
|
| 212 | } |
||
| 213 | |||
| 214 | /** |
||
| 215 | * Prepares the changeset of an entity for database insertion (UPDATE). |
||
| 216 | * |
||
| 217 | * The changeset is obtained from the currently running UnitOfWork. |
||
| 218 | * |
||
| 219 | * During this preparation the array that is passed as the second parameter is filled with |
||
| 220 | * <columnName> => <value> pairs, grouped by table name. |
||
| 221 | * |
||
| 222 | * Example: |
||
| 223 | * <code> |
||
| 224 | * array( |
||
| 225 | * 'foo_table' => array('column1' => 'value1', 'column2' => 'value2', ...), |
||
| 226 | * 'bar_table' => array('columnX' => 'valueX', 'columnY' => 'valueY', ...), |
||
| 227 | * ... |
||
| 228 | * ) |
||
| 229 | * </code> |
||
| 230 | * |
||
| 231 | * @param object $entity The entity for which to prepare the data. |
||
| 232 | * |
||
| 233 | * @return array The prepared data. |
||
| 234 | */ |
||
| 235 | 1 | protected function prepareUpdateData($entity) |
|
| 236 | { |
||
| 237 | 1 | $result = []; |
|
| 238 | 1 | $uow = $this->manager->getUnitOfWork(); |
|
| 239 | 1 | foreach ($uow->getEntityChangeSet($entity) as $field => $change) { |
|
| 240 | 1 | $newVal = $change[1]; |
|
| 241 | 1 | if (!$this->metadata->hasAssociation($field)) { |
|
| 242 | |||
| 243 | $result[$this->metadata->getApiFieldName($field)] = $newVal; |
||
| 244 | continue; |
||
| 245 | } |
||
| 246 | 1 | $assoc = $this->metadata->getAssociationMapping($field); |
|
| 247 | // Only owning side of x-1 associations can have a FK column. |
||
| 248 | 1 | if (!$assoc['isOwningSide'] || !($assoc['type'] & ApiMetadata::TO_ONE)) { |
|
| 249 | continue; |
||
| 250 | } |
||
| 251 | 1 | if ($newVal !== null) { |
|
| 252 | 1 | $oid = spl_object_hash($newVal); |
|
| 253 | 1 | if (isset($this->pendingInserts[$oid]) || $uow->isScheduledForInsert($newVal)) { |
|
| 254 | // The associated entity $newVal is not yet persisted, so we must |
||
| 255 | // set $newVal = null, in order to insert a null value and schedule an |
||
| 256 | // extra update on the UnitOfWork. |
||
| 257 | $uow->scheduleExtraUpdate($entity, [$field => [null, $newVal]]); |
||
| 258 | $newVal = null; |
||
| 259 | } |
||
| 260 | 1 | } |
|
| 261 | 1 | $newValId = null; |
|
| 262 | 1 | if ($newVal !== null) { |
|
| 263 | 1 | $newValId = $uow->getEntityIdentifier($newVal); |
|
| 264 | 1 | } |
|
| 265 | $targetClass = |
||
| 266 | 1 | $this->manager->getClassMetadata($assoc['target']); |
|
| 267 | 1 | $result[$this->metadata->getApiFieldName($field)] = $newValId |
|
| 268 | 1 | ? $newValId[$targetClass->getIdentifierFieldNames()[0]] |
|
| 269 | 1 | : null; |
|
| 270 | 1 | } |
|
| 271 | |||
| 272 | 1 | return $result; |
|
| 273 | } |
||
| 274 | |||
| 275 | 5 | private function convertEntityToData($entity) |
|
| 276 | { |
||
| 277 | 5 | $entityData = []; |
|
| 278 | 5 | foreach ($this->metadata->getReflectionProperties() as $name => $property) { |
|
| 279 | 5 | if ($this->metadata->isIdentifier($name) && $this->metadata->isIdentifierRemote()) { |
|
| 280 | 5 | continue; |
|
| 281 | } |
||
| 282 | 5 | $apiField = $this->metadata->getApiFieldName($name); |
|
| 283 | 5 | $value = $property->getValue($entity); |
|
| 284 | 5 | if (null === $value) { |
|
| 285 | 5 | $entityData[$apiField] = $value; |
|
| 286 | 5 | continue; |
|
| 287 | } |
||
| 288 | |||
| 289 | 2 | if ($this->metadata->hasAssociation($name)) { |
|
| 290 | 2 | $mapping = $this->metadata->getAssociationMapping($name); |
|
| 291 | 2 | if (($mapping['type'] & ApiMetadata::TO_MANY) && !$mapping['isOwningSide']) { |
|
| 292 | 2 | continue; |
|
| 293 | } |
||
| 294 | 2 | $target = $this->metadata->getAssociationMapping($name)['target']; |
|
| 295 | 2 | $targetMetadata = $this->manager->getClassMetadata($target); |
|
| 296 | 2 | $value = $targetMetadata->getIdentifierValues($value); |
|
| 297 | 2 | $ids = []; |
|
| 298 | 2 | foreach ($value as $idName => $idValue) { |
|
| 299 | 2 | $typeName = $targetMetadata->getTypeOfField($idName); |
|
| 300 | 2 | $idApiName = $targetMetadata->getApiFieldName($idName); |
|
| 301 | 2 | $type = $this->manager->getConfiguration()->getTypeRegistry()->get($typeName); |
|
| 302 | 2 | $idValue = $type->toApiValue($idValue); |
|
| 303 | 2 | $ids[$idApiName] = $idValue; |
|
| 304 | 2 | } |
|
| 305 | 2 | if (!$targetMetadata->isIdentifierComposite()) { |
|
|
0 ignored issues
–
show
|
|||
| 306 | 2 | $ids = array_shift($ids); |
|
| 307 | 2 | } |
|
| 308 | 2 | $value = $ids; |
|
| 309 | 2 | } else { |
|
| 310 | $typeName = $this->metadata->getTypeOfField($name); |
||
| 311 | $type = $this->manager->getConfiguration()->getTypeRegistry()->get($typeName); |
||
| 312 | $value = $type->toApiValue($value); |
||
| 313 | } |
||
| 314 | |||
| 315 | 2 | $entityData[$apiField] = $value; |
|
| 316 | 5 | } |
|
| 317 | |||
| 318 | 5 | return $entityData; |
|
| 319 | } |
||
| 320 | } |
||
| 321 |
This check marks calls to methods that do not seem to exist on an object.
This is most likely the result of a method being renamed without all references to it being renamed likewise.