|
<?php |
|
declare(strict_types = 1); |
|
|
|
namespace Ts\examples; |
|
|
|
use Ts\Orm\Data\Map; |
|
|
|
class Book |
|
{ |
|
private $data; |
|
|
|
public function __construct(Map $data) |
|
{ |
|
// Somehow associate a generic dictionary with this entity. This would assume mapping was preconfigured |
|
// somewhere else, and in here we tell this generic dict that it's specific to this Entity / VO. |
|
// At this point, the lib would assert all data constraints: types, required fields, foreign keys, etc. |
|
$this->data = $data->toPersistentMap(self::class); |
|
|
|
// This is another interesting approach. |
|
// Mapping could be read only by instantiating an entity, but could be cached after first call. |
|
// Schema would need to construct mapped objects, but that could be done with some reflection + dummy |
|
// objects on schema tools, without polluting domain classes. |
|
$this->data = $data->toPersistentMap(self::class, function($builder){ |
|
$builder->increments('id'); |
|
$builder->string('title'); |
|
$builder->text('description')->optional(); |
|
$builder->hasMany(Author::class); |
|
$builder->hasMany(Review::class); |
|
}); |
|
|
|
// Constraints-oriented? |
|
$this->data = $data->withConstraints(function(ConstraintFactory $constraints){ |
|
return [ |
|
'id' => $constraints->identity()->autoincremental(), |
|
'title' => $constraints->string(), |
|
'description' => $constraints->string()->optional(), |
|
'authors' => $constraints->hasMany(Author::class)->min(1)->max(4), |
|
'reviews' => $constraints->hasMany(Review::class), |
|
]; |
|
}); |
|
|
|
// Could be rewritten as: |
|
$this->data = $data->toPersistentMap(self::class, [self::class, 'map']); |
|
|
|
// Or the map static method could be a convention/configuration |
|
$this->data = $data->toPersistentMap(self::class); |
|
} |
|
|
|
// If we want to allow static mapping analysis (eg.: schema tools) |
|
public static function map($builder) |
|
{ |
|
$builder->increments('id'); |
|
$builder->string('title'); |
|
$builder->text('description')->optional(); |
|
$builder->hasMany(Author::class); |
|
$builder->hasMany(Review::class); |
|
} |
|
|
|
public function authors() |
|
{ |
|
// This would navigate a preconfigured relation |
|
return $this->data->authors; |
|
// return $this->data->get('authors'); // if you don't like magic props |
|
} |
|
|
|
public function reviews(int $amount = 5) |
|
{ |
|
// Each method would return a collection type object. |
|
// Ideally, this wouldn't query the relation until iterated. |
|
// Also, collection objects would be used as repositories too, so same API |
|
// for relations and "whole table" querying |
|
return $this->data->reviews->orderBy('createdAt')->descending()->take($amount); |
|
} |
|
} |
|
|
|
// Entities / VOs that hold their mapping info themselves could implement a common interface, so detecting them is easier |
|
interface Mapeable |
|
{ |
|
public static function map($builder); |
|
} |
|
|
|
|
|
// Then, somewhere in the mapping lib: |
|
function toPersistentMap($className, callable $mapper = null) { |
|
if ($mapper === null) { |
|
$refl = new ReflectionClass($className); |
|
if (! $refl->implementsInterface(Mapeable::class)) { |
|
throw new YouShouldMapThisObjectBeforeCallingMeException; |
|
} |
|
|
|
$mapper = [$className, 'map']; |
|
} |
|
} |
|
|
|
|
|
// NOTES: |
|
// ------------------ |
|
// // The `Map` looks like a `DAO` from J2EE patterns. Could be misused if the domain is not modeled and |
|
// data access goes straight to the database. |