diff --git a/.gitignore b/.gitignore index 2c8de41..5c3be21 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /vendor/ /node_modules/ composer.lock +.phpunit.cache/ \ No newline at end of file diff --git a/composer.json b/composer.json index 0164688..a770f21 100644 --- a/composer.json +++ b/composer.json @@ -2,6 +2,16 @@ "name": "sofa/eloquence-mappable", "description": "Flexible Searchable, Mappable, Metable, Validation and more extensions for Laravel Eloquent ORM.", "license": "MIT", + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/erasys/eloquence-base" + }, + { + "type": "vcs", + "url": "https://github.com/erasys/hookable" + } + ], "support": { "issues": "https://github.com/jarektkaczyk/eloquence-mappable/issues", "source": "https://github.com/jarektkaczyk/eloquence-mappable" @@ -33,14 +43,14 @@ } ], "require": { - "php": ">=7.1.0", - "sofa/eloquence-base": "dev-7.1.2-erasys as 7.1.2" + "php": ">=8.2", + "sofa/eloquence-base": "8.0.1" }, "require-dev": { "phpunit/phpunit": "^9.5", - "squizlabs/php_codesniffer": "2.3.3", - "mockery/mockery": "^1.0", - "friendsofphp/php-cs-fixer": "^2.0" + "squizlabs/php_codesniffer": "^3.7", + "mockery/mockery": "^1.5", + "friendsofphp/php-cs-fixer": "^3.18.0" }, "autoload": { "psr-4": { @@ -55,7 +65,7 @@ "minimum-stability": "dev", "prefer_stable": true, "scripts": { - "test": "phpunit && ./vendor/bin/phpcs src --standard=psr2 --report=diff --colors", + "test": "./vendor/bin/phpunit && ./vendor/bin/phpcs src --standard=psr2 --report=diff --colors", "phpcs": "./vendor/bin/phpcs src --standard=psr2 --report=diff --colors" } } diff --git a/src/Mappable.php b/src/Mappable.php index bbc5c7b..e4f9708 100644 --- a/src/Mappable.php +++ b/src/Mappable.php @@ -40,7 +40,7 @@ trait Mappable * * @return void */ - public static function bootMappable() + public static function bootMappable() : void { $hooks = new Hooks; @@ -64,7 +64,7 @@ public static function bootMappable() * @param \Sofa\Hookable\Contracts\ArgumentBag $args * @return mixed */ - protected function mappedQuery(Builder $query, $method, ArgumentBag $args) + protected function mappedQuery(Builder $query, $method, ArgumentBag $args) : mixed { $mapping = $this->getMappingForAttribute($args->get('column')); @@ -107,7 +107,7 @@ protected function mappedGroupBy(Builder $query, ArgumentBag $args) * @param \Sofa\Hookable\Contracts\ArgumentBag $args * @return void */ - protected function mappedSelect(Builder $query, ArgumentBag $args) + protected function mappedSelect(Builder $query, ArgumentBag $args) : void { $columns = $args->get('columns'); @@ -154,7 +154,7 @@ protected function mappedSelect(Builder $query, ArgumentBag $args) * @param string $mapping * @return mixed */ - protected function mappedRelationQuery($query, $method, ArgumentBag $args, $mapping) + protected function mappedRelationQuery($query, $method, ArgumentBag $args, $mapping) : mixed { list($target, $column) = $this->parseMappedColumn($mapping); @@ -162,6 +162,10 @@ protected function mappedRelationQuery($query, $method, ArgumentBag $args, $mapp return $this->mappedJoinQuery($query, $method, $args, $target, $column); } + if (in_array($method, ['whereNull', 'whereNotNull'])) { + return $this->mappedWhereQuery($query, $method, $args, $target, $column); + } + return $this->mappedHasQuery($query, $method, $args, $target, $column); } @@ -175,7 +179,7 @@ protected function mappedRelationQuery($query, $method, ArgumentBag $args, $mapp * @param string $column * @return mixed */ - protected function mappedJoinQuery($query, $method, ArgumentBag $args, $target, $column) + protected function mappedJoinQuery($query, $method, ArgumentBag $args, $target, $column) : mixed { $table = $this->joinMapped($query, $target); @@ -198,7 +202,7 @@ protected function mappedJoinQuery($query, $method, ArgumentBag $args, $target, * @param string $target * @return \Sofa\Eloquence\Builder */ - protected function orderByMapped(Builder $query, ArgumentBag $args, $table, $column, $target) + protected function orderByMapped(Builder $query, ArgumentBag $args, $table, $column, $target) : Builder { $query->with($target)->getQuery()->orderBy("{$table}.{$column}", $args->get('direction')); @@ -213,7 +217,7 @@ protected function orderByMapped(Builder $query, ArgumentBag $args, $table, $col * * @return array */ - protected function listsMapped(Builder $query, ArgumentBag $args, $table, $column) + protected function listsMapped(Builder $query, ArgumentBag $args, $table, $column) : array { return $this->pluckMapped($query, $args, $table, $column); } @@ -225,9 +229,9 @@ protected function listsMapped(Builder $query, ArgumentBag $args, $table, $colum * @param \Sofa\Hookable\Contracts\ArgumentBag $args * @param string $table * @param string $column - * @return array + * @return mixed */ - protected function pluckMapped(Builder $query, ArgumentBag $args, $table, $column) + protected function pluckMapped(Builder $query, ArgumentBag $args, $table, $column) : mixed { $query->select("{$table}.{$column}"); @@ -247,7 +251,7 @@ protected function pluckMapped(Builder $query, ArgumentBag $args, $table, $colum * @param string $key * @return \Sofa\Eloquence\Builder */ - protected function mappedSelectListsKey(Builder $query, $key) + protected function mappedSelectListsKey(Builder $query, $key) : Builder { if ($this->hasColumn($key)) { return $query->addSelect($this->getTable() . '.' . $key); @@ -263,7 +267,7 @@ protected function mappedSelectListsKey(Builder $query, $key) * @param string $target * @return string */ - protected function joinMapped(Builder $query, $target) + protected function joinMapped(Builder $query, $target) : string { $query->prefixColumnsForJoin(); @@ -284,7 +288,7 @@ protected function joinMapped(Builder $query, $target) * @param \Illuminate\Database\Eloquent\Model $parent * @return array */ - protected function joinSegment(Builder $query, $segment, EloquentModel $parent) + protected function joinSegment(Builder $query, $segment, EloquentModel $parent) : array { $relation = $parent->{$segment}(); $related = $relation->getRelated(); @@ -319,7 +323,7 @@ protected function joinSegment(Builder $query, $segment, EloquentModel $parent) * @param string $table * @return bool */ - protected function alreadyJoined(Builder $query, $table) + protected function alreadyJoined(Builder $query, $table) : bool { $joined = Arr::pluck((array) $query->getQuery()->joins, 'table'); @@ -334,7 +338,7 @@ protected function alreadyJoined(Builder $query, $table) * * @throws \LogicException */ - protected function getJoinKeys(Relation $relation) + protected function getJoinKeys(Relation $relation) : array { if ($relation instanceof HasOne || $relation instanceof MorphOne) { return [$relation->getQualifiedForeignKeyName(), $relation->getQualifiedParentKeyName()]; @@ -359,7 +363,7 @@ protected function getJoinKeys(Relation $relation) * @param string $qualifiedColumn * @return mixed */ - protected function mappedSingleResult(Builder $query, $method, $qualifiedColumn) + protected function mappedSingleResult(Builder $query, $method, $qualifiedColumn) : mixed { return $query->getQuery()->select("{$qualifiedColumn}")->{$method}("{$qualifiedColumn}"); } @@ -374,7 +378,7 @@ protected function mappedSingleResult(Builder $query, $method, $qualifiedColumn) * @param string $column * @return \Sofa\Eloquence\Builder */ - protected function mappedHasQuery(Builder $query, $method, ArgumentBag $args, $target, $column) + protected function mappedHasQuery(Builder $query, $method, ArgumentBag $args, $target, $column) : Builder { $boolean = $this->getMappedBoolean($args); @@ -386,6 +390,28 @@ protected function mappedHasQuery(Builder $query, $method, ArgumentBag $args, $t ->has($target, $operator, 1, $boolean, $this->getMappedWhereConstraint($method, $args)) ->with($target); } + /** + * Add whereNull/WhereNotNull subquery on the mapped attribute relation. + * + * @param \Sofa\Eloquence\Builder $query + * @param string $method + * @param \Sofa\Hookable\Contracts\ArgumentBag $args + * @param string $target + * @param string $column + * @return \Sofa\Eloquence\Builder + */ + protected function mappedWhereQuery(Builder $query, $method, ArgumentBag $args, $target, $column) : Builder + { + $boolean = $this->getMappedBoolean($args); + $operator = $this->getMappedOperator($method, $args); + + $args->set('columns', $column); + $args->unset('column'); + + return $query + ->has($target, $operator, 1, $boolean, $this->getMappedWhereConstraint($method, $args)) + ->with($target); + } /** * Get the relation constraint closure. @@ -394,7 +420,7 @@ protected function mappedHasQuery(Builder $query, $method, ArgumentBag $args, $t * @param \Sofa\Hookable\Contracts\ArgumentBag $args * @return \Closure */ - protected function getMappedWhereConstraint($method, ArgumentBag $args) + protected function getMappedWhereConstraint($method, ArgumentBag $args) : \Closure { return function ($query) use ($method, $args) { call_user_func_array([$query, $method], $args->all()); @@ -407,7 +433,7 @@ protected function getMappedWhereConstraint($method, ArgumentBag $args) * @param \Sofa\EloquenceArgumentBag $args * @return string */ - protected function getMappedBoolean(ArgumentBag $args) + protected function getMappedBoolean(ArgumentBag $args) : string { $boolean = $args->get('boolean'); @@ -423,7 +449,7 @@ protected function getMappedBoolean(ArgumentBag $args) * @param \Sofa\Hookable\Contracts\ArgumentBag $args * @return string */ - protected function getMappedOperator($method, ArgumentBag $args) + protected function getMappedOperator($method, ArgumentBag $args) : string { if ($not = $args->get('not')) { $args->set('not', false); @@ -442,11 +468,12 @@ protected function getMappedOperator($method, ArgumentBag $args) * @param string $key * @return string|null */ - public function getMappingForAttribute($key) + public function getMappingForAttribute($key) : ?string { if ($this->hasMapping($key)) { return $this->mappedAttributes[$key]; } + return null; } /** @@ -455,9 +482,9 @@ public function getMappingForAttribute($key) * @param string $mapping * @return bool */ - protected function relationMapping($mapping) + protected function relationMapping($mapping) : bool { - return strpos($mapping, '.') !== false; + return strpos((string)$mapping, '.') !== false; } /** @@ -466,7 +493,7 @@ protected function relationMapping($mapping) * @param string $key * @return bool */ - public function hasMapping($key) + public function hasMapping($key) : bool { if (is_null($this->mappedAttributes)) { $this->parseMappings(); @@ -480,7 +507,7 @@ public function hasMapping($key) * * @return void */ - protected function parseMappings() + protected function parseMappings() : void { $this->mappedAttributes = []; @@ -500,7 +527,7 @@ protected function parseMappings() * @param string $target * @return void */ - protected function parseImplicitMapping($attributes, $target) + protected function parseImplicitMapping($attributes, $target) : void { foreach ($attributes as $attribute) { $this->mappedAttributes[$attribute] = "{$target}.{$attribute}"; @@ -513,7 +540,7 @@ protected function parseImplicitMapping($attributes, $target) * @param string $key * @return mixed */ - protected function mapAttribute($key) + protected function mapAttribute($key) : mixed { $segments = explode('.', $this->getMappingForAttribute($key)); @@ -527,11 +554,11 @@ protected function mapAttribute($key) * @param array $segments * @return mixed */ - protected function getTarget($target, array $segments) + protected function getTarget($target, array $segments) : mixed { foreach ($segments as $segment) { if (!$target) { - return; + return null; } $target = $target->{$segment}; @@ -546,7 +573,7 @@ protected function getTarget($target, array $segments) * @param string $key * @param mixed $value */ - protected function setMappedAttribute($key, $value) + protected function setMappedAttribute($key, $value) : void { $segments = explode('.', $this->getMappingForAttribute($key)); @@ -564,7 +591,7 @@ protected function setMappedAttribute($key, $value) * * @param \Illuminate\Database\Eloquent\Model $target */ - protected function addTargetToSave($target) + protected function addTargetToSave($target) : void { if ($this !== $target) { $this->targetsToSave[] = $target; @@ -576,7 +603,7 @@ protected function addTargetToSave($target) * * @return void */ - protected function saveMapped() + protected function saveMapped() : void { foreach (array_unique($this->targetsToSave) as $target) { $target->save(); @@ -591,7 +618,7 @@ protected function saveMapped() * @param string $key * @return void */ - protected function forget($key) + protected function forget($key) : void { $mapping = $this->getMappingForAttribute($key); @@ -610,7 +637,7 @@ protected function forget($key) * * @inheritdoc */ - protected function mutateAttributeForArray($key, $value) + protected function mutateAttributeForArray($key, $value) : mixed { if ($this->hasMapping($key)) { $value = $this->mapAttribute($key); @@ -626,7 +653,7 @@ protected function mutateAttributeForArray($key, $value) * * @return array */ - public function getMaps() + public function getMaps() : array { return (property_exists($this, 'maps')) ? $this->maps : []; } diff --git a/tests/MappableTest.php b/tests/MappableTest.php index 6294112..1803108 100644 --- a/tests/MappableTest.php +++ b/tests/MappableTest.php @@ -403,6 +403,10 @@ class MappableStub saveMapped as protectedSaveMapped; } + public $bar; + + public $related; + protected $maps = [ // local alias 'alias' => 'original', // $this->original