/** * @param string $definition * @param string $alias * @param string $type * @throws InvalidArgumentException */ public function addJoinByType($definition, $alias, $type) { list($fromAlias, $viaProperty) = $this->parseDotNotation($definition); $entityReflection = $this->getReflection($fromEntity = $this->aliases->getEntityClass($fromAlias)); $property = $entityReflection->getEntityProperty($viaProperty); if (!$property->hasRelationship()) { throw new InvalidArgumentException(); } $relationship = $property->getRelationship(); if ($relationship instanceof HasMany) { $this->clauses->join[] = array('type' => $type, 'joinParameters' => array($relationshipTable = $relationship->getRelationshipTable(), $relTableAlias = $relationshipTable . $this->indexer), 'onParameters' => array($fromAlias, $primaryKey = $this->mapper->getPrimaryKey($fromTable = $this->mapper->getTable($fromEntity)), $relTableAlias, $columnReferencingSourceTable = $relationship->getColumnReferencingSourceTable())); $this->hydratorMeta->addTablePrefix($relTableAlias, $relationshipTable); $this->hydratorMeta->addPrimaryKey($relationshipTable, $relTablePrimaryKey = $this->mapper->getPrimaryKey($relationshipTable)); $this->hydratorMeta->addRelationship($alias, new Relationship($fromAlias, $fromTable, $columnReferencingSourceTable, Relationship::DIRECTION_REFERENCING, $relTableAlias, $relationshipTable, $primaryKey)); $this->clauses->join[] = array('type' => $type, 'joinParameters' => array($targetTable = $relationship->getTargetTable(), $alias), 'onParameters' => array($relTableAlias, $columnReferencingTargetTable = $relationship->getColumnReferencingTargetTable(), $alias, $primaryKey = $this->mapper->getPrimaryKey($targetTable))); $this->aliases->addAlias($alias, $property->getType()); $this->hydratorMeta->addTablePrefix($alias, $targetTable); $this->hydratorMeta->addPrimaryKey($targetTable, $primaryKey); $this->hydratorMeta->addRelationship($relTableAlias, new Relationship($relTableAlias, $relationshipTable, $columnReferencingTargetTable, Relationship::DIRECTION_REFERENCED, $alias, $targetTable, $primaryKey)); $this->relationshipTables[$alias] = array($relTableAlias, $relTablePrimaryKey, $relTableAlias . QueryHelper::PREFIX_SEPARATOR . $relTablePrimaryKey, $relTableAlias, $columnReferencingSourceTable, $relTableAlias . QueryHelper::PREFIX_SEPARATOR . $columnReferencingSourceTable, $relTableAlias, $columnReferencingTargetTable, $relTableAlias . QueryHelper::PREFIX_SEPARATOR . $columnReferencingTargetTable); $this->indexer++; } else { $this->clauses->join[] = array('type' => $type, 'joinParameters' => array($targetTable = $relationship->getTargetTable(), $alias), 'onParameters' => $relationship instanceof HasOne ? array($fromAlias, $relationshipColumn = $relationship->getColumnReferencingTargetTable(), $alias, $primaryKey = $this->mapper->getPrimaryKey($targetTable)) : array($fromAlias, $primaryKey = $this->mapper->getPrimaryKey($fromTable = $this->mapper->getTable($fromEntity)), $alias, $columnReferencingSourceTable = $relationship->getColumnReferencingSourceTable())); $this->aliases->addAlias($alias, $property->getType()); $this->hydratorMeta->addTablePrefix($alias, $targetTable); if ($relationship instanceof HasOne) { $this->hydratorMeta->addPrimaryKey($targetTable, $primaryKey); $this->hydratorMeta->addRelationship($alias, new Relationship($fromAlias, $this->mapper->getTable($fromEntity), $relationshipColumn, Relationship::DIRECTION_REFERENCED, $alias, $targetTable, $primaryKey)); } else { $this->hydratorMeta->addPrimaryKey($targetTable, $targetTablePrimaryKey = $this->mapper->getPrimaryKey($targetTable)); $this->hydratorMeta->addRelationship($fromAlias, new Relationship($fromAlias, $fromTable, $columnReferencingSourceTable, Relationship::DIRECTION_REFERENCING, $alias, $targetTable, $primaryKey)); } } }
private function replaceEntitiesForItsPrimaryKeyValues(array $entities) { foreach ($entities as &$entity) { if ($entity instanceof LeanMapper\Entity) { $entityTable = $this->mapper->getTable(get_class($entity)); // FIXME: Column name could be specified in the entity instead of mapper provided by 'getEntityField' function. $idField = $this->mapper->getEntityField($entityTable, $this->mapper->getPrimaryKey($entityTable)); $entity = $entity->{$idField}; } } return $entities; }
/** * @param Entity $entity * @return mixed */ private function getIdValue(Entity $entity) { $table = $this->getTable(); do { $primaryKey = $this->mapper->getPrimaryKey($table); $idField = $this->mapper->getEntityField($table, $primaryKey); $value = $entity->{$idField}; if (!$value instanceof Entity) { return $value; } $entity = $value; $table = $this->mapper->getTable(get_class($entity)); } while (true); }
/** * @return Entity[] */ public function getEntities($limit = null, $offset = null) { if ($this->entities === NULL) { $entities = array(); $entityClass = $this->clauses->from['entityClass']; $result = $this->getResult($this->clauses->from['alias'], $limit, $offset); $primaryKey = $this->mapper->getPrimaryKey($this->mapper->getTable($entityClass)); foreach ($result as $key => $row) { $entity = $this->entityFactory->createEntity($entityClass, new Row($result, $key)); $entities[$entity->{$primaryKey}] = $entity; $entity->makeAlive($this->entityFactory, $this->connection, $this->mapper); $entity->cleanReferencingRowsCache(); } $this->entities = $this->entityFactory->createCollection($entities); } return $this->entities; }
/** * @param string $action * @param string $name * @param mixed $arg * @throws InvalidMethodCallException * @throws InvalidArgumentException * @throws InvalidValueException */ private function addToOrRemoveFrom($action, $name, $arg) { if ($this->isDetached()) { throw new InvalidMethodCallException('Cannot add or remove related entity to detached entity.'); } if ($arg === null) { throw new InvalidArgumentException('Invalid argument given in entity ' . get_called_class() . '.'); } if (is_array($arg) or $arg instanceof Traversable and !$arg instanceof Entity) { foreach ($arg as $value) { $this->addToOrRemoveFrom($action, $name, $value); } } else { $method = $action === self::ACTION_ADD ? 'addTo' : 'removeFrom'; $property = $this->getCurrentReflection()->getEntityProperty($name); if ($property === null or !$property->hasRelationship() or !$property->getRelationship() instanceof Relationship\HasMany) { throw new InvalidMethodCallException("Cannot call {$method} method with '{$name}' property in entity " . get_called_class() . '. Only properties with m:hasMany relationship can be managed this way.'); } if ($property->getFilters()) { throw new InvalidMethodCallException("Cannot call {$method} method with '{$name}' property in entity " . get_called_class() . '. Only properties without filters can be managed this way.'); // deliberate restriction } $relationship = $property->getRelationship(); if ($arg instanceof Entity) { if ($arg->isDetached()) { throw new InvalidArgumentException('Cannot add or remove detached entity ' . get_class($arg) . " to {$name} in entity " . get_called_class() . '.'); } $type = $property->getType(); if (!$arg instanceof $type) { $type = gettype($arg) !== 'object' ? gettype($arg) : 'instance of ' . get_class($arg); throw new InvalidValueException("Unexpected value type given in property '{$property->getName()}' in entity " . get_called_class() . ", {$property->getType()} expected, {$type} given."); } $data = $arg->getRowData(); $arg = $data[$this->mapper->getPrimaryKey($relationship->getTargetTable())]; } $table = $this->mapper->getTable($this->getCurrentReflection()->getName()); $values = [$relationship->getColumnReferencingSourceTable() => $this->row->{$this->mapper->getPrimaryKey($table)}, $relationship->getColumnReferencingTargetTable() => $arg]; $method .= 'Referencing'; $this->row->{$method}($values, $relationship->getRelationshipTable(), $relationship->getColumnReferencingSourceTable(), null, $relationship->getStrategy()); } }
/** * @param string $sourceClass * @param PropertyType $propertyType * @param string $relationshipType * @param string|null $definition * @param IMapper|null $mapper * @return mixed * @throws InvalidAnnotationException */ private static function createRelationship($sourceClass, PropertyType $propertyType, $relationshipType, $definition = null, IMapper $mapper = null) { if ($relationshipType !== 'hasOne') { $strategy = Result::STRATEGY_IN; // default strategy if ($definition !== null and substr($definition, -6) === '#union') { $strategy = Result::STRATEGY_UNION; $definition = substr($definition, 0, -6); } } $pieces = array_replace(array_fill(0, 6, ''), $definition !== null ? explode(':', $definition) : []); $sourceTable = $mapper !== null ? $mapper->getTable($sourceClass) : null; $targetTable = $mapper !== null ? $mapper->getTable($propertyType->getType()) : null; switch ($relationshipType) { case 'hasOne': $relationshipColumn = $mapper !== null ? $mapper->getRelationshipColumn($sourceTable, $targetTable) : self::getSurrogateRelationshipColumn($propertyType); return new Relationship\HasOne($pieces[0] ?: $relationshipColumn, $pieces[1] ?: $targetTable); case 'hasMany': return new Relationship\HasMany($pieces[0] ?: ($mapper !== null ? $mapper->getRelationshipColumn($mapper->getRelationshipTable($sourceTable, $targetTable), $sourceTable) : null), $pieces[1] ?: ($mapper !== null ? $mapper->getRelationshipTable($sourceTable, $targetTable) : null), $pieces[2] ?: ($mapper !== null ? $mapper->getRelationshipColumn($mapper->getRelationshipTable($sourceTable, $targetTable), $targetTable) : null), $pieces[3] ?: $targetTable, $strategy); case 'belongsToOne': $relationshipColumn = $mapper !== null ? $mapper->getRelationshipColumn($targetTable, $sourceTable) : $sourceTable; return new Relationship\BelongsToOne($pieces[0] ?: $relationshipColumn, $pieces[1] ?: $targetTable, $strategy); case 'belongsToMany': $relationshipColumn = $mapper !== null ? $mapper->getRelationshipColumn($targetTable, $sourceTable) : $sourceTable; return new Relationship\BelongsToMany($pieces[0] ?: $relationshipColumn, $pieces[1] ?: $targetTable, $strategy); } return null; }
public function createSchema(array $entities) { $config = array_merge($this->defaultConfig, $this->options); $schema = new Schema(); Type::addType(LongTextType::LONG_TEXT, '\\Joseki\\Migration\\Generator\\DBAL\\Types\\LongTextType'); Type::addType(TimestampType::TIMESTAMP, '\\Joseki\\Migration\\Generator\\DBAL\\Types\\TimestampType'); $createdTables = []; /** @var \LeanMapper\Entity $entity */ foreach ($entities as $entity) { $reflection = $entity->getReflection($this->mapper); $properties = $reflection->getEntityProperties(); $onEnd = []; if (count($properties) === 0) { continue; } $tableName = $this->mapper->getTable(get_class($entity)); $table = $schema->createTable($tableName); $table->addOption('collate', $config['collate']); $primaryKey = $this->mapper->getPrimaryKey($tableName); foreach ($properties as $property) { /** @var Property $property */ if ($this->isIgnored($property)) { continue; } if (!$property->hasRelationship()) { if (!$property->isWritable()) { continue; } $type = $this->getType($property); if ($type === null) { throw new \Exception('Unknown type'); } /** @var Column $column */ $column = $table->addColumn($property->getColumn(), $type); if ($property->getName() == $primaryKey) { $table->setPrimaryKey([$property->getColumn()]); if ($property->hasCustomFlag('unique')) { throw new Exception\InvalidAnnotationException("Entity {$reflection->name}:{$property->getName()} - m:unique can not be used together with m:pk."); } if ($config['autoincrement'] == 'auto' && $type === 'integer') { $column->setAutoincrement(true); } } if ($property->hasCustomFlag('autoincrement')) { $column->setAutoincrement(true); } if ($type == 'string' && $property->hasCustomFlag('size')) { $column->setLength($property->getCustomFlagValue('size')); } if ($type == TimestampType::TIMESTAMP) { $flagArgs = explode(':', strtolower($property->getCustomFlagValue('type'))); if (count($flagArgs) > 1 && $flagArgs[1] == 'true') { $column->setColumnDefinition('TIMESTAMP NOT NULL ON UPDATE CURRENT_TIMESTAMP'); } else { $column->setColumnDefinition('TIMESTAMP NOT NULL'); } } } else { $relationship = $property->getRelationship(); if ($relationship instanceof HasMany) { $relationshipTableName = $relationship->getRelationshipTable(); if (!in_array($relationshipTableName, $createdTables)) { $createdTables[] = $relationshipTableName; $relationshipTable = $schema->createTable($relationship->getRelationshipTable()); $cascade = $config['cascading'] ? 'CASCADE' : 'NO ACTION'; $columnReferencingSourceTable = $relationship->getColumnReferencingSourceTable(); $sourceTableType = $this->getRelationshipColumnType($columnReferencingSourceTable); $sourceColumn = $relationshipTable->addColumn($columnReferencingSourceTable, $sourceTableType); $primaryKey1 = $this->mapper->getPrimaryKey($tableName); $columnReferencingTargetTable = $relationship->getColumnReferencingTargetTable(); $targetTableType = $this->getRelationshipColumnType($columnReferencingTargetTable); $targetColumn = $relationshipTable->addColumn($columnReferencingTargetTable, $targetTableType); $primaryKey2 = $this->mapper->getPrimaryKey($relationship->getTargetTable()); $relationshipTable->addForeignKeyConstraint($table, [$columnReferencingSourceTable], [$primaryKey1], ['onDelete' => $cascade, 'onUpdate' => $cascade]); $relationshipTable->addForeignKeyConstraint($relationship->getTargetTable(), [$columnReferencingTargetTable], [$primaryKey2], ['onDelete' => $cascade, 'onUpdate' => $cascade]); $sourceColumnProperty = $this->getRelationshipColumnProperty($tableName); if ($this->getType($sourceColumnProperty) === 'string' && $sourceColumnProperty->hasCustomFlag('size')) { $sourceColumn->setLength($sourceColumnProperty->getCustomFlagValue('size')); } $targetColumnProperty = $this->getRelationshipColumnProperty($relationship->getTargetTable()); if ($this->getType($targetColumnProperty) === 'string' && $targetColumnProperty->hasCustomFlag('size')) { $targetColumn->setLength($targetColumnProperty->getCustomFlagValue('size')); } } } elseif ($relationship instanceof HasOne) { $targetEntityClass = $property->getType(); $targetTable = $this->mapper->getTable($targetEntityClass); $targetType = $this->getRelationshipColumnType($targetTable); $column = $table->addColumn($relationship->getColumnReferencingTargetTable(), $targetType); $targetColumnProperty = $this->getRelationshipColumnProperty($targetTable); if ($this->getType($targetColumnProperty) === 'string' && $targetColumnProperty->hasCustomFlag('size')) { $column->setLength($targetColumnProperty->getCustomFlagValue('size')); } if (!$property->hasCustomFlag('nofk')) { $onDeleteCascade = $config['cascading'] ? $property->isNullable() ? 'SET NULL' : 'CASCADE' : 'NO ACTION'; $onUpdateCascade = $config['cascading'] ? $property->isNullable() ? 'SET NULL' : 'CASCADE' : 'NO ACTION'; $table->addForeignKeyConstraint($relationship->getTargetTable(), [$column->getName()], [$this->mapper->getPrimaryKey($relationship->getTargetTable())], ['onDelete' => $onDeleteCascade, 'onUpdate' => $onUpdateCascade]); } } } if ($property->hasCustomFlag('unique')) { $indexColumns = $this->parseColumns($property->getCustomFlagValue('unique'), [$column->getName()]); $onEnd[] = $this->createIndexClosure($table, $indexColumns, true); } if ($property->hasCustomFlag('index')) { $indexColumns = $this->parseColumns($property->getCustomFlagValue('index'), [$column->getName()]); $onEnd[] = $this->createIndexClosure($table, $indexColumns, false); } if ($property->hasCustomFlag('comment')) { $column->setComment($property->getCustomFlagValue('comment')); } if (isset($column)) { if ($property->isNullable()) { $column->setNotnull(false); } if ($property->hasDefaultValue()) { $column->setDefault($property->getDefaultValue()); } } } foreach ($onEnd as $cb) { $cb(); } } return $schema; }