Exemplo n.º 1
0
 /**
  * Perform a deserialization
  *
  * @param array|Closure $serializationProvider An anonymized serialization by array or generator
  * @return array An array consisting of the encountered internal ids as keys, and created db ids as values
  * @throws Exception on invalid rules
  */
 public function deserialize($serializationProvider)
 {
     if (is_array($serializationProvider)) {
         // Pretend the array is an generator
         $generator = function () use($serializationProvider) {
             return [$serializationProvider];
         };
     } elseif ($serializationProvider instanceof Closure) {
         // Assume the given closure is a generator
         $generator = $serializationProvider;
     } else {
         throw new Exception("Provided serialization provider must be either an array or a generator");
     }
     // Crank out the serialized data
     foreach ($generator() as $serialization) {
         // Count entities for simple progress functionality
         $entityCount = 0;
         foreach ($serialization as $items) {
             $entityCount += count($items);
         }
         // Set the goal to include all entities
         $this->state->setProgressGoal($entityCount);
         // Iterate through the serialization data Array<FQCN, Array<Serialized entity>>
         foreach ($serialization as $fqcn => $serializedEntities) {
             $this->state->push($fqcn);
             // Debug
             // Iterate through the serialized entities
             foreach ($serializedEntities as $serializedEntity) {
                 $model = $this->app->make($fqcn);
                 // Get the internal id of this entity
                 $id = $serializedEntity["@id"];
                 // Has primary key in data?
                 if (isset($serializedEntity[$model->getKeyName()])) {
                     // Get database id
                     $dbId = $serializedEntity[$model->getKeyName()];
                     // Set manually
                     $model->id = $dbId;
                     $model->exists = true;
                 }
                 // Get blueprints for this model
                 $blueprint = $this->getBlueprint($model, $serializedEntity);
                 // If blueprint is false, we don't want to deserialize at all
                 if ($blueprint === false) {
                     continue;
                 }
                 foreach ($blueprint as $rule) {
                     if (is_array($rule)) {
                         $field = $rule[0];
                     } else {
                         $field = $rule;
                     }
                     $this->state->push($field);
                     if (!method_exists($model, $field)) {
                         // Non-relation
                         // If the rule is an array, it's supposed contain a set of rules
                         if (is_array($rule) && is_array($serializedEntity[$field])) {
                             // Match rules for this field found, figure out how to use them
                             if (count($rule) === 2 && is_array($serializedEntity[$field])) {
                                 // Rules
                                 // $rule[0] = field name
                                 // $rule[1] = array of rules
                                 // serialized content = array
                                 $model->{$field} = $this->toDbIds($model, $id, $field, $serializedEntity[$field], $rule[1]);
                             } elseif (count($rule) === 3) {
                                 // Conditional Rules
                                 // $rule[0] = field name
                                 // $rule[1] = field name to match condition against
                                 // $rule[2] = associative array where key is the value to match the field against,
                                 //            and the value is the array of rules to use when a match is found
                                 $matchAgainst = $serializedEntity[$rule[1]];
                                 if (isset($rule[2][$matchAgainst])) {
                                     $model->{$field} = $this->toDbIds($model, $id, $field, $serializedEntity[$field], $rule[2][$matchAgainst]);
                                 } else {
                                     $model->{$field} = $serializedEntity[$field];
                                 }
                             } else {
                                 throw new Exception("Unsupported rule with weird array count of " . count($rule));
                             }
                         } else {
                             // The rule or content weren't arrays, so we just want to save the content
                             $model->{$field} = $serializedEntity[$field];
                         }
                     } else {
                         // The field is a relation
                         $relation = $model->{$field}();
                         $relationName = last(explode("\\", get_class($relation)));
                         switch ($relationName) {
                             case "BelongsTo":
                                 // Don't associate nulls
                                 if (isset($serializedEntity[$field])) {
                                     // Associate or defer an association
                                     $this->bookKeeper->associate($model, $field, $id, $serializedEntity[$field]);
                                 }
                                 break;
                             case "MorphTo":
                                 // Morph or defer a morph
                                 $this->bookKeeper->morph($model, $field, $id, $serializedEntity[$field]);
                                 break;
                         }
                     }
                     $this->state->pop();
                     // Debug
                 }
                 // Handle progress
                 $this->state->incrementProgress();
                 // Create/Update the database entity
                 $model->save();
                 // Report the real db id
                 $this->bookKeeper->bind($id, $model->getKey());
             }
             $this->state->pop();
             // Debug
         }
     }
     // Resolve all deferred actions and return the book keeping Array<Internal id, Database id>
     return $this->bookKeeper->resolve();
 }
Exemplo n.º 2
0
 /**
  * Perform a serialization
  *
  * @param Model $model Initially the top level model to which all of your other models are related
  * @param array $serialization Serialization tree being built
  *
  * @return array
  * @throws Exception
  */
 public function serialize(Model $model, array $serialization = [])
 {
     $fqcn = get_class($model);
     $serializedEntity = [];
     // Get blueprints for this model
     $blueprint = $this->getBlueprint($model);
     // If blueprint is false, we don't want to serialize at all
     if ($blueprint === false) {
         return $serialization;
     }
     $this->state->push("{$fqcn}-" . $model->getKey());
     // Debug
     // Set internal id (Directly from book keeper, don't use getIdRef)
     $serializedEntity["@id"] = $this->bookKeeper->getId($fqcn, $model->getKey());
     // If blueprint is an associative array (as opposed to a normal array) we just want to merge with @id and continue
     $blueprintKeys = array_keys($blueprint);
     if (count($blueprintKeys) > 0 && is_string(reset($blueprintKeys))) {
         // First key is a string - Assume the whole thing is an associative array
         $serializedEntity = array_merge($serializedEntity, $blueprint);
         // [@id => @123] -> [@id => @123, foo => bar, ...]
         // Add to the "big" serialized array and return early
         $serialization[$fqcn][] = $serializedEntity;
         return $serialization;
     }
     // Make space in the serialized array for entities of this model type
     if (!isset($serialization[$fqcn])) {
         $serialization[$fqcn] = [];
     }
     // Iterate through the blueprint rules
     foreach ($blueprint as $rule) {
         if (is_array($rule)) {
             $field = $rule[0];
         } else {
             $field = $rule;
         }
         $this->state->push($field);
         // Debug
         // Get content for the given field from the model
         $content = $model->{$field};
         // Is some transformation involved?
         $transformer = null;
         if (method_exists($model, $field)) {
             // Yes, look at the transforming method by running it
             $transformer = $model->{$field}();
         }
         // Do different things depending on the content type
         if (is_scalar($content) || is_null($content) || is_array($content)) {
             if (is_array($rule) && is_array($content)) {
                 // Rules for this field found, figure out how to use them
                 // [field, [match rules]] or [field, field to match against, [match1 => [match rules], match2 => ...]]
                 if (count($rule) === 2) {
                     // Match rules
                     // [field, [match rules]]
                     // $rule[0] = field name
                     // $rule[1] = array of match rules
                     // $content = array
                     $serializedEntity[$field] = $this->toInternalIds($content, $rule[1]);
                 } else {
                     if (count($rule) === 3) {
                         // Conditional rules
                         // [field, field to match against, [match1 => [match rules], match2 => ...]]
                         // $rule[0] = field name
                         // $rule[1] = field name to match condition against
                         // $rule[2] = associative array where key is the value to match the field against,
                         //            and the value is the array of rules to use when a match is found
                         $matchAgainst = $model->{$rule[1]};
                         if (isset($rule[2][$matchAgainst])) {
                             $serializedEntity[$field] = $this->toInternalIds($content, $rule[2][$matchAgainst]);
                         } else {
                             $serializedEntity[$field] = $content;
                         }
                     } else {
                         throw new Exception("Unsupported rule with weird array count of " . count($rule));
                     }
                 }
             } else {
                 // The rule or content weren't arrays, so we just want to save the content
                 $serializedEntity[$field] = $content;
             }
         } elseif ($content instanceof Model) {
             // The content is a model, implying a one-to-one relationship
             $contentFqcn = get_class($content);
             $relation = $transformer;
             $relationName = last(explode("\\", get_class($relation)));
             // Handle different relationships differently
             switch ($relationName) {
                 case "BelongsTo":
                     // Do we already have this belonging entity's internal id?
                     if ($this->bookKeeper->hasId($contentFqcn, $content->{$relation->getOtherKey()})) {
                         // Yep, use it and continue
                         $serializedEntity[$field] = $this->getIdRef($contentFqcn, $content->{$relation->getOtherKey()});
                     } else {
                         // Nope, create it and recurse down the rabbit hole
                         $serializedEntity[$field] = $this->getIdRef($contentFqcn, $content->{$relation->getOtherKey()});
                         $serialization = $this->serialize($content, $serialization);
                     }
                     break;
                 case "HasOne":
                     // Recurse down the rabbit hole
                     $serialization = $this->serialize($content, $serialization);
                     break;
                 case "MorphTo":
                     // Do we already have this morphed entity's internal id?
                     if ($this->bookKeeper->hasId($contentFqcn, $content->{$relation->getOtherKey()})) {
                         // Yep, use it and continue
                         $serializedEntity[$field] = $this->getIdRef($contentFqcn, $content->{$relation->getOtherKey()});
                     } else {
                         // Nope, create it and recurse down the rabbit hole
                         $serializedEntity[$field] = $this->getIdRef($contentFqcn, $content->{$relation->getOtherKey()});
                         $serialization = $this->serialize($content, $serialization);
                     }
                     // Get morphable id and morphable type and save as a tuple [type, id]
                     $morphedId = $model->{$relation->getForeignKey()};
                     $morphedType = $model->{$relation->getMorphType()};
                     $serializedEntity[$field] = [$morphedType, $this->getIdRef($morphedType, $morphedId)];
                     break;
             }
         } elseif ($content instanceof Collection) {
             // The content is a Collection, implying a one-to-many relationship
             $relation = $model->{$field}();
             $relationName = last(explode("\\", get_class($relation)));
             switch ($relationName) {
                 case "HasMany":
                 case "MorphMany":
                 case "MorphToMany":
                     // Iterate through the related entities
                     foreach ($content as $child) {
                         $serialization = $this->serialize($child, $serialization);
                     }
                     break;
             }
         }
         $this->state->pop();
         // Debug
     }
     // Abort if entity is already processed
     // @todo Inefficient to do these checks at this point and not earlier
     foreach ($serialization[$fqcn] as $entity) {
         if ($entity["@id"] === $serializedEntity["@id"]) {
             $this->state->pop();
             // Debug
             return $serialization;
         }
     }
     // Give the event listeners a chance to have a say
     if (isset($this->onBeforeAddToTree[$fqcn])) {
         // Run the callable
         $mutatedTree = $this->onBeforeAddToTree[$fqcn]($serializedEntity);
         // Did it return an array?
         if (is_array($mutatedTree)) {
             // Overwrite our original serialized entity data
             $serializedEntity = $mutatedTree;
         } else {
             if ($mutatedTree === false) {
                 // Did it return a false?
                 // Don't add the serialized entity at all - early return
                 $this->state->pop();
                 // Debug
                 // Return the serialization
                 return $serialization;
             }
         }
     }
     // Add entity to the serialization
     $serialization[$fqcn][] = $serializedEntity;
     $this->state->pop();
     // Debug
     // Return the serialization
     return $serialization;
 }