diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 12:38:04 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 12:38:04 +0000 |
commit | 1ff5c35de5dbd70a782875a91dd2232fd01b002b (patch) | |
tree | 77d9ce5e1bf78b3e6ef79f8f6e7861e2ced3c09b /vendor/ipl/orm/src/Relation | |
parent | Initial commit. (diff) | |
download | icinga-php-library-db00f2fd38bc3063de0d7b1412949bc50f248a1f.tar.xz icinga-php-library-db00f2fd38bc3063de0d7b1412949bc50f248a1f.zip |
Adding upstream version 0.10.1.upstream/0.10.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | vendor/ipl/orm/src/Relation.php | 336 | ||||
-rw-r--r-- | vendor/ipl/orm/src/Relation/BelongsTo.php | 13 | ||||
-rw-r--r-- | vendor/ipl/orm/src/Relation/BelongsToMany.php | 199 | ||||
-rw-r--r-- | vendor/ipl/orm/src/Relation/HasMany.php | 13 | ||||
-rw-r--r-- | vendor/ipl/orm/src/Relation/HasOne.php | 12 | ||||
-rw-r--r-- | vendor/ipl/orm/src/Relation/Junction.php | 43 | ||||
-rw-r--r-- | vendor/ipl/orm/src/Relations.php | 212 |
7 files changed, 828 insertions, 0 deletions
diff --git a/vendor/ipl/orm/src/Relation.php b/vendor/ipl/orm/src/Relation.php new file mode 100644 index 0000000..1959363 --- /dev/null +++ b/vendor/ipl/orm/src/Relation.php @@ -0,0 +1,336 @@ +<?php + +namespace ipl\Orm; + +use function ipl\Stdlib\get_php_type; + +/** + * Relations represent the connection between models, i.e. the association between rows in one or more tables + * on the basis of matching key columns. The relationships are defined using candidate key-foreign key constructs. + */ +class Relation +{ + /** @var string Name of the relation */ + protected $name; + + /** @var Model Source model */ + protected $source; + + /** @var string|array Column name(s) of the foreign key found in the target table */ + protected $foreignKey; + + /** @var string|array Column name(s) of the candidate key in the source table which references the foreign key */ + protected $candidateKey; + + /** @var string Target model class */ + protected $targetClass; + + /** @var Model Target model */ + protected $target; + + /** @var string Type of the JOIN used in the query */ + protected $joinType = 'INNER'; + + /** @var bool Whether this is the inverse of a relationship */ + protected $inverse; + + /** @var bool Whether this is a to-one relationship */ + protected $isOne = true; + + /** + * Get the default column name(s) in the source table used to match the foreign key + * + * The default candidate key is the primary key column name(s) of the given model. + * + * @param Model $source + * + * @return array + */ + public static function getDefaultCandidateKey(Model $source) + { + return (array) $source->getKeyName(); + } + + /** + * Get the default column name(s) of the foreign key found in the target table + * + * The default foreign key is the given model's primary key column name(s) prefixed with its table name. + * + * @param Model $source + * + * @return array + */ + public static function getDefaultForeignKey(Model $source) + { + $tableName = $source->getTableName(); + + return array_map( + function ($key) use ($tableName) { + return "{$tableName}_{$key}"; + }, + (array) $source->getKeyName() + ); + } + + /** + * Get whether this is a to-one relationship + * + * @return bool + */ + public function isOne() + { + return $this->isOne; + } + + /** + * Get the name of the relation + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Set the name of the relation + * + * @param string $name + * + * @return $this + */ + public function setName($name) + { + $this->name = $name; + + return $this; + } + + /** + * Get the source model of the relation + * + * @return Model + */ + public function getSource() + { + return $this->source; + } + + /** + * Set the source model of the relation + * + * @param Model $source + * + * @return $this + */ + public function setSource(Model $source) + { + $this->source = $source; + + return $this; + } + + /** + * Get the column name(s) of the foreign key found in the target table + * + * @return string|array Array if the foreign key is compound, string otherwise + */ + public function getForeignKey() + { + return $this->foreignKey; + } + + /** + * Set the column name(s) of the foreign key found in the target table + * + * @param string|array $foreignKey Array if the foreign key is compound, string otherwise + * + * @return $this + */ + public function setForeignKey($foreignKey) + { + $this->foreignKey = $foreignKey; + + return $this; + } + + /** + * Get the column name(s) of the candidate key in the source table which references the foreign key + * + * @return string|array Array if the candidate key is compound, string otherwise + */ + public function getCandidateKey() + { + return $this->candidateKey; + } + + /** + * Set the column name(s) of the candidate key in the source table which references the foreign key + * + * @param string|array $candidateKey Array if the candidate key is compound, string otherwise + * + * @return $this + */ + public function setCandidateKey($candidateKey) + { + $this->candidateKey = $candidateKey; + + return $this; + } + + /** + * Get the target model class + * + * @return string + */ + public function getTargetClass() + { + return $this->targetClass; + } + + /** + * Set the target model class + * + * @param string $targetClass + * + * @return $this + * + * @throws \InvalidArgumentException If the target model class is not of type string + */ + public function setTargetClass($targetClass) + { + if (! is_string($targetClass)) { + // Require a class name here instead of a concrete model in oder to prevent circular references when + // constructing relations + throw new \InvalidArgumentException(sprintf( + '%s() expects parameter 1 to be string, %s given', + __METHOD__, + get_php_type($targetClass) + )); + } + + $this->targetClass = $targetClass; + + return $this; + } + + /** + * Get the target model + * + * Returns the model from {@link setTarget()} or an instance of {@link getTargetClass()}. + * Note that multiple calls to this method always returns the very same model instance. + * + * @return Model + */ + public function getTarget() + { + if ($this->target === null) { + $targetClass = $this->getTargetClass(); + $this->target = new $targetClass(); + } + + return $this->target; + } + + /** + * Set the the target model + * + * @param Model $target + * + * @return $this + */ + public function setTarget(Model $target) + { + $this->target = $target; + + return $this; + } + + /** + * Get the type of the JOIN used in the query + * + * @return string + */ + public function getJoinType() + { + return $this->joinType; + } + + /** + * Set the type of the JOIN used in the query + * + * @param string $joinType + * + * @return Relation + */ + public function setJoinType($joinType) + { + $this->joinType = $joinType; + + return $this; + } + + /** + * Determine the candidate key-foreign key construct of the relation + * + * @param Model $source + * + * @return array Candidate key-foreign key column name pairs + * + * @throws \UnexpectedValueException If there's no candidate key to be found + * or the foreign key count does not match the candidate key count + */ + public function determineKeys(Model $source) + { + $candidateKey = (array) $this->getCandidateKey(); + + if (empty($candidateKey)) { + $candidateKey = $this->inverse + ? static::getDefaultForeignKey($this->getTarget()) + : static::getDefaultCandidateKey($source); + } + + if (empty($candidateKey)) { + throw new \UnexpectedValueException(sprintf( + "Can't join relation '%s' in model '%s'. No candidate key found.", + $this->getName(), + get_class($source) + )); + } + + $foreignKey = (array) $this->getForeignKey(); + + if (empty($foreignKey)) { + $foreignKey = $this->inverse + ? static::getDefaultCandidateKey($this->getTarget()) + : static::getDefaultForeignKey($source); + } + + if (count($foreignKey) !== count($candidateKey)) { + throw new \UnexpectedValueException(sprintf( + "Can't join relation '%s' in model '%s'." + . " Foreign key count (%s) does not match candidate key count (%s).", + $this->getName(), + get_class($source), + implode(', ', $foreignKey), + implode(', ', $candidateKey) + )); + } + + return array_combine($foreignKey, $candidateKey); + } + + /** + * Resolve the relation + * + * Yields a three-element array consisting of the source model, target model and the join keys. + * + * @return \Generator + */ + public function resolve() + { + $source = $this->getSource(); + + yield [$source, $this->getTarget(), $this->determineKeys($source)]; + } +} diff --git a/vendor/ipl/orm/src/Relation/BelongsTo.php b/vendor/ipl/orm/src/Relation/BelongsTo.php new file mode 100644 index 0000000..1edcff3 --- /dev/null +++ b/vendor/ipl/orm/src/Relation/BelongsTo.php @@ -0,0 +1,13 @@ +<?php + +namespace ipl\Orm\Relation; + +use ipl\Orm\Relation; + +/** + * Inverse of a one-to-one or one-to-many relationship + */ +class BelongsTo extends Relation +{ + protected $inverse = true; +} diff --git a/vendor/ipl/orm/src/Relation/BelongsToMany.php b/vendor/ipl/orm/src/Relation/BelongsToMany.php new file mode 100644 index 0000000..132fe55 --- /dev/null +++ b/vendor/ipl/orm/src/Relation/BelongsToMany.php @@ -0,0 +1,199 @@ +<?php + +namespace ipl\Orm\Relation; + +use ipl\Orm\Model; +use ipl\Orm\Relation; +use ipl\Orm\Relations; + +use function ipl\Stdlib\get_php_type; + +/** + * Many-to-many relationship + */ +class BelongsToMany extends Relation +{ + protected $isOne = false; + + /** @var string Name of the join table or junction model class */ + protected $throughClass; + + /** @var Model The junction model */ + protected $through; + + /** @var string|array Column name(s) of the target model's foreign key found in the join table */ + protected $targetForeignKey; + + /** @var string|array Candidate key column name(s) in the target table which references the target foreign key */ + protected $targetCandidateKey; + + /** + * Get the name of the join table or junction model class + * + * @return string + */ + public function getThroughClass() + { + return $this->throughClass; + } + + /** + * Set the join table name or junction model class + * + * @param string $through + * + * @return $this + */ + public function through($through) + { + $this->throughClass = $through; + + return $this; + } + + /** + * Get the junction model + * + * @return Model|Junction + */ + public function getThrough() + { + if ($this->through === null) { + $throughClass = $this->getThroughClass(); + + if (class_exists($throughClass)) { + $this->through = new $throughClass(); + } else { + $this->through = (new Junction()) + ->setTableName($throughClass); + } + } + + return $this->through; + } + + /** + * Set the junction model + * + * @param Model $through + * + * @return $this + */ + public function setThrough($through) + { + $this->through = $through; + + return $this; + } + + /** + * Get the column name(s) of the target model's foreign key found in the join table + * + * @return string|array Array if the foreign key is compound, string otherwise + */ + public function getTargetForeignKey() + { + return $this->targetForeignKey; + } + + /** + * Set the column name(s) of the target model's foreign key found in the join table + * + * @param string|array $targetForeignKey Array if the foreign key is compound, string otherwise + * + * @return $this + */ + public function setTargetForeignKey($targetForeignKey) + { + $this->targetForeignKey = $targetForeignKey; + + return $this; + } + + /** + * Get the candidate key column name(s) in the target table which references the target foreign key + * + * @return string|array Array if the foreign key is compound, string otherwise + */ + public function getTargetCandidateKey() + { + return $this->targetCandidateKey; + } + + /** + * Set the candidate key column name(s) in the target table which references the target foreign key + * + * @param string|array $targetCandidateKey Array if the foreign key is compound, string otherwise + * + * @return $this + */ + public function setTargetCandidateKey($targetCandidateKey) + { + $this->targetCandidateKey = $targetCandidateKey; + + return $this; + } + + public function resolve() + { + $source = $this->getSource(); + + $possibleCandidateKey = [$this->getCandidateKey()]; + $possibleForeignKey = [$this->getForeignKey()]; + + $target = $this->getTarget(); + + $possibleTargetCandidateKey = [$this->getTargetForeignKey() ?: static::getDefaultForeignKey($target)]; + $possibleTargetForeignKey = [$this->getTargetCandidateKey() ?: static::getDefaultCandidateKey($target)]; + + $junction = $this->getThrough(); + + if (! $junction instanceof Junction) { + $relations = new Relations(); + $junction->createRelations($relations); + + if ($relations->has($source->getTableName())) { + $sourceRelation = $relations->get($source->getTableName()); + + $possibleCandidateKey[] = $sourceRelation->getForeignKey(); + $possibleForeignKey[] = $sourceRelation->getCandidateKey(); + } + + if ($relations->has($target->getTableName())) { + $targetRelation = $relations->get($target->getTableName()); + + $possibleTargetCandidateKey[] = $targetRelation->getCandidateKey(); + $possibleTargetForeignKey[] = $targetRelation->getForeignKey(); + } + } + + $toJunction = (new HasMany()) + ->setName($junction->getTableName()) + ->setSource($source) + ->setTarget($junction) + ->setCandidateKey($this->extractKey($possibleCandidateKey)) + ->setForeignKey($this->extractKey($possibleForeignKey)); + + $toTarget = (new HasMany()) + ->setName($this->getName()) + ->setSource($junction) + ->setTarget($target) + ->setCandidateKey($this->extractKey($possibleTargetCandidateKey)) + ->setForeignKey($this->extractKey($possibleTargetForeignKey)); + + foreach ($toJunction->resolve() as $k => $v) { + yield $k => $v; + } + + foreach ($toTarget->resolve() as $k => $v) { + yield $k => $v; + } + } + + protected function extractKey(array $possibleKey) + { + $filtered = array_filter($possibleKey); + + return array_pop($filtered); + } +} diff --git a/vendor/ipl/orm/src/Relation/HasMany.php b/vendor/ipl/orm/src/Relation/HasMany.php new file mode 100644 index 0000000..3e71e25 --- /dev/null +++ b/vendor/ipl/orm/src/Relation/HasMany.php @@ -0,0 +1,13 @@ +<?php + +namespace ipl\Orm\Relation; + +use ipl\Orm\Relation; + +/** + * One-to-many relationship + */ +class HasMany extends Relation +{ + protected $isOne = false; +} diff --git a/vendor/ipl/orm/src/Relation/HasOne.php b/vendor/ipl/orm/src/Relation/HasOne.php new file mode 100644 index 0000000..8f7a802 --- /dev/null +++ b/vendor/ipl/orm/src/Relation/HasOne.php @@ -0,0 +1,12 @@ +<?php + +namespace ipl\Orm\Relation; + +use ipl\Orm\Relation; + +/** + * One-to-one relationship + */ +class HasOne extends Relation +{ +} diff --git a/vendor/ipl/orm/src/Relation/Junction.php b/vendor/ipl/orm/src/Relation/Junction.php new file mode 100644 index 0000000..9e23bb2 --- /dev/null +++ b/vendor/ipl/orm/src/Relation/Junction.php @@ -0,0 +1,43 @@ +<?php + +namespace ipl\Orm\Relation; + +use ipl\Orm\Model; + +/** + * Junction model for many-to-many relations + */ +class Junction extends Model +{ + /** @var string */ + protected $tableName; + + public function getTableName() + { + return $this->tableName; + } + + /** + * Set the table name + * + * @param string $tableName + * + * @return $this + */ + public function setTableName($tableName) + { + $this->tableName = $tableName; + + return $this; + } + + public function getKeyName() + { + return null; + } + + public function getColumns() + { + return null; + } +} diff --git a/vendor/ipl/orm/src/Relations.php b/vendor/ipl/orm/src/Relations.php new file mode 100644 index 0000000..340435f --- /dev/null +++ b/vendor/ipl/orm/src/Relations.php @@ -0,0 +1,212 @@ +<?php + +namespace ipl\Orm; + +use ArrayIterator; +use ipl\Orm\Exception\InvalidRelationException; +use ipl\Orm\Relation\BelongsTo; +use ipl\Orm\Relation\BelongsToMany; +use ipl\Orm\Relation\HasMany; +use ipl\Orm\Relation\HasOne; +use IteratorAggregate; +use Traversable; + +use function ipl\Stdlib\get_php_type; + +/** + * Collection of a model's relations. + */ +class Relations implements IteratorAggregate +{ + /** @var Relation[] */ + protected $relations = []; + + /** + * Get whether a relation with the given name exists + * + * @param string $name + * + * @return bool + */ + public function has($name) + { + return isset($this->relations[$name]); + } + + /** + * Get the relation with the given name + * + * @param string $name + * + * @return Relation + * + * @throws \InvalidArgumentException If the relation with the given name does not exist + */ + public function get($name) + { + $this->assertRelationExists($name); + + return $this->relations[$name]; + } + + /** + * Add the given relation to the collection + * + * @param Relation $relation + * + * @return $this + * + * @throws \InvalidArgumentException If a relation with the given name already exists + */ + public function add(Relation $relation) + { + $name = $relation->getName(); + + $this->assertRelationDoesNotYetExist($name); + + $this->relations[$name] = $relation; + + return $this; + } + + /** + * Create a new relation from the given class, name and target model class + * + * @param string $class Class of the relation to create + * @param string $name Name of the relation + * @param string $targetClass Target model class + * + * @return BelongsTo|BelongsToMany|HasMany|HasOne|Relation + * + * @throws \InvalidArgumentException If the target model class is not of type string + */ + public function create($class, $name, $targetClass) + { + $relation = new $class(); + + if (! $relation instanceof Relation) { + throw new \InvalidArgumentException(sprintf( + '%s() expects parameter 1 to be a subclass of %s, %s given', + __METHOD__, + Relation::class, + get_php_type($relation) + )); + } + + // Test target model + $target = new $targetClass(); + if (! $target instanceof Model) { + throw new \InvalidArgumentException(sprintf( + '%s() expects parameter 3 to be a subclass of %s, %s given', + __METHOD__, + Model::class, + get_php_type($target) + )); + } + + /** @var Relation $relation */ + $relation + ->setName($name) + ->setTarget($target) + ->setTargetClass($targetClass); + + return $relation; + } + + /** + * Define a one-to-one relationship + * + * @param string $name Name of the relation + * @param string $targetClass Target model class + * + * @return HasOne + */ + public function hasOne($name, $targetClass) + { + $relation = $this->create(HasOne::class, $name, $targetClass); + + $this->add($relation); + + return $relation; + } + + /** + * Define a one-to-many relationship + * + * @param string $name Name of the relation + * @param string $targetClass Target model class + * + * @return HasMany + */ + public function hasMany($name, $targetClass) + { + $relation = $this->create(HasMany::class, $name, $targetClass); + + $this->add($relation); + + return $relation; + } + + /** + * Define the inverse of a one-to-one or one-to-many relationship + * + * @param string $name Name of the relation + * @param string $targetClass Target model class + * + * @return BelongsTo + */ + public function belongsTo($name, $targetClass) + { + $relation = $this->create(BelongsTo::class, $name, $targetClass); + + $this->add($relation); + + return $relation; + } + + /** + * Define a many-to-many relationship + * + * @param string $name Name of the relation + * @param string $targetClass Target model class + * + * @return BelongsToMany + */ + public function belongsToMany($name, $targetClass) + { + $relation = $this->create(BelongsToMany::class, $name, $targetClass); + + $this->add($relation); + + return $relation; + } + + public function getIterator(): Traversable + { + return new ArrayIterator($this->relations); + } + + /** + * Throw exception if a relation with the given name already exists + * + * @param string $name + */ + protected function assertRelationDoesNotYetExist($name) + { + if ($this->has($name)) { + throw new \InvalidArgumentException("Relation '$name' already exists"); + } + } + + /** + * Throw exception if a relation with the given name does not exist + * + * @param string $name + */ + protected function assertRelationExists($name) + { + if (! $this->has($name)) { + throw new InvalidRelationException($name); + } + } +} |