-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add model extensions * fix method-call-style relations * clean up code * add fascade * add fascade * remove facade, add to model * remove unused imports * add documentation * remove imports * add tests * styleci * fix injected relations * add more tests * add testbench dependency * test eager loading
- Loading branch information
1 parent
c367d47
commit 673c6cf
Showing
17 changed files
with
629 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of SeAT | ||
* | ||
* Copyright (C) 2015 to present Leon Jacobs | ||
* | ||
* This program is free software; you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License as published by | ||
* the Free Software Foundation; either version 2 of the License, or | ||
* (at your option) any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU General Public License along | ||
* with this program; if not, write to the Free Software Foundation, Inc., | ||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
*/ | ||
|
||
namespace Seat\Services\Exceptions; | ||
|
||
use Exception; | ||
|
||
class InjectedRelationConflictException extends Exception | ||
{ | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of SeAT | ||
* | ||
* Copyright (C) 2015 to present Leon Jacobs | ||
* | ||
* This program is free software; you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License as published by | ||
* the Free Software Foundation; either version 2 of the License, or | ||
* (at your option) any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU General Public License along | ||
* with this program; if not, write to the Free Software Foundation, Inc., | ||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
*/ | ||
|
||
namespace Seat\Services\Models; | ||
|
||
use Illuminate\Contracts\Container\BindingResolutionException; | ||
use Illuminate\Database\Eloquent\Model; | ||
use Illuminate\Database\Eloquent\Relations\Relation; | ||
use LogicException; | ||
use Seat\Services\Exceptions\InjectedRelationConflictException; | ||
use Seat\Services\Services\InjectedRelationRegistry; | ||
|
||
abstract class ExtensibleModel extends Model | ||
{ | ||
/** | ||
* Returns an attribute or relation of the model, considering injected relations. | ||
* | ||
* @param string $key | ||
* @return mixed | ||
* | ||
* @throws BindingResolutionException | ||
*/ | ||
public function __get($key) | ||
{ | ||
// fetch injected relations | ||
$extension_registry = app()->make(InjectedRelationRegistry::class); | ||
$extension_class = $extension_registry->getExtensionClassFor($this::class, $key); | ||
|
||
// check if we have an injected relation | ||
if($extension_class){ | ||
// we have an injected relation | ||
// since what we are doing is not intended, we have to roughly reimplement laravel's code from here on | ||
|
||
//check if relation data is cached | ||
// the relation cache continues to work even for 'fake' relations, but we have to manually call it | ||
if($this->relationLoaded($key)){ | ||
return $this->getRelationValue($key); | ||
} | ||
|
||
// it is NOT cached, we have to load it and put it into the cache | ||
// get relation from extension | ||
$extension_class_instance = new $extension_class; | ||
$relation = $extension_class_instance->$key($this); | ||
|
||
// the following code is taken from laravel's \Illuminate\Database\Eloquent\Concerns\HasAttributes::getRelationshipFromMethod | ||
// check if we actually got a relation returned | ||
if (! $relation instanceof Relation) { | ||
if (is_null($relation)) { | ||
throw new LogicException(sprintf( | ||
'%s::%s must return a relationship instance, but "null" was returned. Was the "return" keyword used?', static::class, $key | ||
)); | ||
} | ||
|
||
throw new LogicException(sprintf( | ||
'%s::%s must return a relationship instance.', static::class, $key | ||
)); | ||
} | ||
|
||
return tap($relation->getResults(), function ($results) use ($key) { | ||
$this->setRelation($key, $results); | ||
}); | ||
} | ||
|
||
// use the default behaviour if no relation is injected | ||
return parent::__get($key); | ||
} | ||
|
||
/** | ||
* Redirects calls to injected relations or behaves like a normal class. | ||
* | ||
* @param string $method | ||
* @param $parameters | ||
* @return mixed | ||
* | ||
* @throws BindingResolutionException | ||
*/ | ||
public function __call($method, $parameters) | ||
{ | ||
// fetch injected relations | ||
$extension_registry = app()->make(InjectedRelationRegistry::class); | ||
$extension_class = $extension_registry->getExtensionClassFor($this::class, $method); | ||
|
||
// check if we have an injected relation | ||
if($extension_class) { | ||
// return the injected relation | ||
$extension_class_instance = new $extension_class; | ||
|
||
return $extension_class_instance->$method($this); | ||
} | ||
|
||
// use the default behaviour if no relation is injected | ||
return parent::__call($method, $parameters); | ||
} | ||
|
||
/** | ||
* Injects relations into this model. | ||
* | ||
* @param string $extension_class the class that provides the injected relations | ||
* @return void | ||
* | ||
* @throws BindingResolutionException | ||
* @throws InjectedRelationConflictException A conflict arises when trying to inject two relations with the same name into a target. | ||
*/ | ||
public static function injectRelationsFrom(string $extension_class): void { | ||
$registry = app()->make(InjectedRelationRegistry::class); | ||
$registry->injectRelations(static::class, $extension_class); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of SeAT | ||
* | ||
* Copyright (C) 2015 to present Leon Jacobs | ||
* | ||
* This program is free software; you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License as published by | ||
* the Free Software Foundation; either version 2 of the License, or | ||
* (at your option) any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU General Public License along | ||
* with this program; if not, write to the Free Software Foundation, Inc., | ||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
*/ | ||
|
||
namespace Seat\Services\Services; | ||
|
||
use Seat\Services\Exceptions\InjectedRelationConflictException; | ||
|
||
class InjectedRelationRegistry | ||
{ | ||
/** | ||
* Lookup table to search for injected relations. | ||
* Maps the result from getInjectionTargetKey to a class. | ||
* | ||
* @var array<string, string> | ||
*/ | ||
private array $relations = []; | ||
|
||
/** | ||
* Injects all relations from a class into a model. | ||
* | ||
* @param string $target_model the model to inject relations into | ||
* @param string $extension_class the class to take the relations from | ||
* @return void | ||
* | ||
* @throws InjectedRelationConflictException A conflict arises when trying to inject two relations with the same name into a target. | ||
*/ | ||
public function injectRelations(string $target_model, string $extension_class): void | ||
{ | ||
$methods = get_class_methods($extension_class); | ||
|
||
foreach ($methods as $relation_name){ | ||
$this->injectSingleRelation($target_model, $extension_class, $relation_name); | ||
} | ||
} | ||
|
||
/** | ||
* Injects a single relation into a model. | ||
* | ||
* @param string $model the model to inject the relation into | ||
* @param string $extension_class the class holding the relation function | ||
* @param string $relation the name of the relation to be injected. The method providing the relation in $extension_class must have the same name, and the relation will be accessible under this name. | ||
* @return void | ||
* | ||
* @throws InjectedRelationConflictException A conflict arises when trying to inject two relations with the same name into a target. | ||
*/ | ||
public function injectSingleRelation(string $model, string $extension_class, string $relation): void | ||
{ | ||
$key = $this->getInjectionTargetKey($model, $relation); | ||
|
||
// check for conflicts, as there can't be two relations with the same name | ||
if(array_key_exists($key, $this->relations)) { | ||
$conflict = $this->relations[$key]; | ||
throw new InjectedRelationConflictException(sprintf('Relation \'%s\' from \'%s\' is name-conflicting with \'%s\'', $relation, $model, $conflict)); | ||
} | ||
|
||
$this->relations[$key] = $extension_class; | ||
} | ||
|
||
/** | ||
* Searches for injected relations for a model. | ||
* | ||
* @param string $model the model to search for | ||
* @param string $relation the relation name to search for | ||
* @return string|null the class providing the injected relation, or null if there is no injected relation | ||
*/ | ||
public function getExtensionClassFor(string $model, string $relation): ?string | ||
{ | ||
return $this->relations[$this->getInjectionTargetKey($model, $relation)] ?? null; | ||
} | ||
|
||
/** | ||
* Generates a key for $this->$relations. | ||
* | ||
* @param string $model the injection target class | ||
* @param string $relation_name the relation name | ||
* @return string a key to use with $this->$relations | ||
*/ | ||
private function getInjectionTargetKey(string $model, string $relation_name): string { | ||
return sprintf('%s.%s', $model, $relation_name); | ||
} | ||
} |
Oops, something went wrong.