/** * {@inheritDoc} */ public function createMetadata($reference) { // clear the method arrays $this->getters = []; $this->setters = []; $this->adders = []; $ref = new \ReflectionClass($reference); $this->findClayMethods($ref); $entityMetadata = new EntityMetadata($ref->getName()); /* For each setter (or adder) that has a getter (e.g. a property that is fully accessible) check if it has a relationship to another entity class, otherwise detect the property type */ foreach ($this->setters as $property => $setterMethod) { if (empty($this->getters[$property])) { // can't continue without a getter continue; } /** @var \ReflectionMethod $setterMethod */ /** @var \ReflectionParameter $valueParam */ $valueParam = $setterMethod->getParameters()[0]; if ($valueParam->isArray()) { // this is a collection, check for an adder so we can check type on the collection elements if (!empty($this->adders[$property])) { $setterMethod = $this->adders[$property]; $arguments = $setterMethod->getParameters(); // take the last argument, as the value parameter will be #2 for adders that apply to associative arrays $valueParam = end($arguments); } } $class = $valueParam->getClass(); // ignore properties that have relationships with other entities if (empty($class) || empty($class->getNamespaceName())) { /** @var \ReflectionMethod $getterMethod */ $getterMethod = $this->getters[$property]; $getter = $getterMethod->getName(); $setter = $setterMethod->getName(); if ($valueParam->isArray()) { $type = EntityMetadata::FIELD_TYPE_ARRAY; } else { // detect type $type = $this->detectPropertyType($this->owningClass[$property], $getter, $setter, $property); } $fieldMetadata = [EntityMetadata::METADATA_FIELD_TYPE => $type, EntityMetadata::METADATA_FIELD_GETTER => $getter, EntityMetadata::METADATA_FIELD_SETTER => $setter]; // underscore the property name $property = $this->toSplitCase($property); $entityMetadata->addFieldMetadata($property, $fieldMetadata); } } return $entityMetadata; }
protected function addIncludes(TokenSequencerInterface $query, $includeRelationships = null) { $includeRelationships = is_null($includeRelationships) ? $this->includeRelationshipsByDefault : $includeRelationships; if (!empty($includeRelationships)) { if (!is_array($includeRelationships) || isset($includeRelationships[0])) { $includeRelationships = ["this" => $includeRelationships]; } $allRelationships = ["this" => $this->entityMetadata->getRelationships()]; $relationships = []; $parents = []; // add all the relationship metadata we need into the allRelationships array foreach (array_keys($includeRelationships) as $alias) { if ($alias == "this") { // already added the subject entity's relationships continue; } foreach ($allRelationships as $subset) { if (!empty($subset[$alias])) { $relationshipEntity = $subset[$alias][EntityMetadata::METADATA_ENTITY]; $relationshipMetadata = $this->metadataProvider->getEntityMetadata($relationshipEntity); $allRelationships[$alias] = $relationshipMetadata->getRelationships(); break; } } } foreach ($includeRelationships as $alias => $includes) { if (empty($allRelationships[$alias])) { continue; } $thisRelationships = $allRelationships[$alias]; // if we have an array of includes, filter thisRelationships if (is_array($includes)) { $thisRelationships = array_intersect_key($allRelationships[$alias], array_flip($includes)); } // record the parent of each of the child aliases, so we can reference them when we include the entity foreach (array_keys($thisRelationships) as $childAlias) { $parents[$childAlias] = $alias; } // add thisRelationships to the final list $relationships = array_replace($relationships, $thisRelationships); } // get the metadata for each entity and include it on the query foreach ($relationships as $alias => $relationship) { $metadata = $this->metadataProvider->getEntityMetadata($relationship[EntityMetadata::METADATA_ENTITY]); if ($alias == $metadata->getEntity()) { $alias = ""; } $parent = ""; if (!empty($parents[$alias]) && $parents[$alias] != "this") { $parent = $parents[$alias]; } $query->includeEntity($metadata, $alias, $parent); } } }
/** * @dataProvider relationshipProvider * * @param array $relationships * @param array $expectedPatterns * @param array $unexpectedPatterns * @param string $intermediaryCollection */ public function testProcessingRelationships(array $relationships, array $expectedPatterns, array $unexpectedPatterns = [], $intermediaryCollection = "") { $outputDir = vfsStream::url($this->testDir); $this->metadata->shouldReceive("getFields")->andReturn(["id" => [EntityMetadata::METADATA_FIELD_TYPE => EntityMetadata::FIELD_TYPE_INT]]); $this->metadata->shouldReceive("getPrimaryKey")->andReturn("id"); $this->metadata->shouldReceive("getRelationships")->andReturn($relationships); if (!empty($intermediaryCollection)) { // setup intermediary metadata $this->intermediaryMetadata->shouldReceive("getCollection")->andReturn($intermediaryCollection); } $generator = new MigrationGenerator($this->metadataProvider, $outputDir); $fileName = $generator->generateMigrationFor("Test\\Entity"); $this->assertFileExists($fileName); $fileContents = file_get_contents($fileName); foreach ($expectedPatterns as $pattern) { $this->assertRegExp($pattern, $fileContents); } foreach ($unexpectedPatterns as $pattern) { $this->assertNotRegExp($pattern, $fileContents); } }
public function includeEntity(EntityMetadata $childMetadata, $collectionAlias = "", $parent = "", TokenSequencerInterface $additionalFilters = null) { // check we're dealing with a query if (!$this->isQuery()) { throw new TokenParseException("Cannot include an entity on an expression sequence."); } // get data on the child entity $childEntity = $childMetadata->getEntity(); $childCollection = $childMetadata->getCollection(); $childAlias = empty($collectionAlias) ? $childCollection : $collectionAlias; // determine which entity to join on (the parent) $parentMetadata = $this->getEntityMetadata(); if (!empty($parent)) { if (empty($this->includes[$parent])) { throw new TokenParseException("Cannot include the entity '{$childEntity}'. The parent entity '{$parent}' has not yet been included"); } /** @var EntityMetadata $parentMetadata */ $parentMetadata = $this->includes[$parent]; if ($parent == $parentMetadata->getEntity()) { $parent = $parentMetadata->getCollection(); } } // make sure we have a parent that we can use as a table name or alias if (empty($parent) || $parent == $parentMetadata->getEntity()) { $parent = $parentMetadata->getCollection(); } // get the relationship between the child and parent $relationshipAlias = empty($collectionAlias) ? $childEntity : $collectionAlias; $relationship = $parentMetadata->getRelationship($relationshipAlias); if (empty($relationship)) { throw new TokenParseException("The parent entity '{$parentMetadata->getEntity()}' has no relationship defined for '{$relationshipAlias}'"); } // find the fields we will need to use in the join condition // we need the primary key and/or the parent field defined in the relationship $parentKey = $parentMetadata->getPrimaryKey(); $parentField = $parent . "." . (empty($relationship[EntityMetadata::METADATA_RELATIONSHIP_OUR_FIELD]) ? $parentKey : $relationship[EntityMetadata::METADATA_RELATIONSHIP_OUR_FIELD]); // we need the primary key and/or the child field defined in the relationship $childKey = $childMetadata->getPrimaryKey(); $childField = $childAlias . "." . (empty($relationship[EntityMetadata::METADATA_RELATIONSHIP_THEIR_FIELD]) ? $childKey : $relationship[EntityMetadata::METADATA_RELATIONSHIP_THEIR_FIELD]); // many to many relationships require an extra join, so treat them differently if ($relationship[EntityMetadata::METADATA_RELATIONSHIP_TYPE] != EntityMetadata::RELATIONSHIP_TYPE_MANY_TO_MANY) { // create the join condition $onClause = new TokenSequencer($this->tokenFactory); $onClause->ref($parentField)->op("=")->ref($childField); // if we have additional join filters, add them now if (!empty($additionalFilters)) { $onClause->andL()->mergeSequence($additionalFilters->getSequence()); } // create the join $this->join($childCollection, $onClause, $collectionAlias); } else { // many to many. Requires intermediary join table $joinTable = $relationship[EntityMetadata::METADATA_RELATIONSHIP_JOIN_TABLE]; // create the join condition from the parent to the intermediate $parentToMany = new TokenSequencer($this->tokenFactory); $parentToMany->ref($parentField)->op("=")->ref("{$joinTable}.{$parent}_id"); // create the join condition from the intermediate to the child, including any additional join filters $manyToChild = new TokenSequencer($this->tokenFactory); $manyToChild->ref("{$joinTable}.{$childAlias}_id")->op("=")->ref($childField); if (!empty($additionalFilters)) { $manyToChild->andL()->mergeSequence($additionalFilters->getSequence()); } // create the joins $this->join($joinTable, $parentToMany)->join($childCollection, $manyToChild, $collectionAlias); } // add the include $this->includes[$childAlias] = $childMetadata; return $this; }
/** * @dataProvider pkMetadataProvider * * @param $fieldMetadata * @param $expectedMetadata */ public function testPublicKeyMetadata($fieldMetadata, $expectedMetadata) { $pk = "id"; $metadata = new EntityMetadata("blah"); $metadata->setPrimaryKey($pk); if (!is_null($fieldMetadata)) { $metadata->addFieldMetadata($pk, $fieldMetadata); } $pkMetadata = $metadata->getPrimaryKeyMetadata(); foreach ($expectedMetadata as $field => $value) { $this->assertEquals($value, $pkMetadata[$field]); } }