/** * 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(); }