TL;DR
車輪の再発明してしまったのでソースコードの墓場にする記事
内容
データベースを使わないけど, app/Services
配下に置くような外部 API サービスのレスポンスエンティティを Eloquent ライクに使いたいニーズがあった。
最初 Eloquent Model を継承することも考えたが,意味のないメソッドがごちゃごちゃ入ってくるのが鬱陶しいので
HasAttributes
HidesAttributes
GuardsAttributes
の3つのトレイトをベースに,足りないものや動作修正の必要があるものを適宜ケアして,最小限の記述で作ってみたのでここにぶん投げます。IDE でチェックしながらメソッド埋めやったのでエラーとかは起こらないはず…
<?php
namespace App\AbstractEntities;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Database\Eloquent\Concerns\GuardsAttributes;
use Illuminate\Database\Eloquent\Concerns\HasAttributes;
use Illuminate\Database\Eloquent\Concerns\HidesAttributes;
use Illuminate\Database\Eloquent\MassAssignmentException;
use Illuminate\Support\Collection;
use LogicException;
abstract class Entity implements Arrayable
{
use HasAttributes, HidesAttributes, GuardsAttributes;
public const CREATED_AT = 'created_at';
public const UPDATED_AT = 'updated_at';
/**
* @var bool
*/
public $timestamps = true;
/**
* @var bool
*/
public $incrementing = true;
/**
* @var string
*/
protected $primaryKey = 'id';
/**
* @var string
*/
protected $keyType = 'int';
/**
* @var array
*/
protected $relations = [];
/**
* Create an existing Entity instance.
*
* @param array $attributes
* @return static
*/
public static function hydrate(array $attributes = [])
{
return (new static())->forceFill($attributes)->syncOriginal();
}
/**
* Create existing Entity instances.
*
* @param array $attributesArray
* @return \Illuminate\Support\Collection|static[]
*/
public static function hydrateMany(array $attributesArray = []): Collection
{
$collection = new Collection();
foreach ($attributesArray as $key => $attributes) {
$collection[$key] = static::hydrate($attributes);
}
return $collection;
}
/**
* Create a new Entity instance.
*
* @param array $attributes
*/
public function __construct(array $attributes = [])
{
$this->fill($attributes);
}
/**
* Apply updates to an existing Entity instance.
*
* @param array $attributes
* @return $this
*/
public function apply(array $attributes)
{
return $this->forceFill($attributes)->syncOriginal();
}
/**
* Fill the model with an array of attributes.
*
* @param array $attributes
* @throws \Illuminate\Database\Eloquent\MassAssignmentException
* @return $this
*/
public function fill(array $attributes)
{
$totallyGuarded = $this->totallyGuarded();
foreach ($this->fillableFromArray($attributes) as $key => $value) {
if ($this->isFillable($key)) {
$this->setAttribute($key, $value);
} elseif ($totallyGuarded) {
throw new MassAssignmentException(sprintf(
'Add [%s] to fillable property to allow mass assignment on [%s].',
$key,
get_class($this)
));
}
}
return $this;
}
/**
* Fill the model with an array of attributes. Force mass assignment.
*
* @param array $attributes
* @return $this
*/
public function forceFill(array $attributes)
{
return static::unguarded(function () use ($attributes) {
return $this->fill($attributes);
});
}
/**
* Determine if the given relation is loaded.
*
* @param string $key
* @return bool
*/
public function relationLoaded(string $key): bool
{
return array_key_exists($key, $this->relations);
}
/**
* Set the given relationship on the model.
*
* @param string $key
* @param mixed $value
* @return $this
*/
public function setRelation(string $key, $value)
{
$this->relations[$key] = $value;
return $this;
}
/**
* Determine if the model uses timestamps.
*
* @return bool
*/
public function usesTimestamps(): bool
{
return $this->timestamps;
}
/**
* Get the value indicating whether the IDs are incrementing.
*
* @return bool
*/
public function getIncrementing(): bool
{
return $this->incrementing;
}
/**
* Get the primary key for the model.
*
* @return string
*/
public function getKeyName(): string
{
return $this->primaryKey;
}
/**
* Get the auto-incrementing key type.
*
* @return string
*/
public function getKeyType(): string
{
return $this->keyType;
}
/**
* Get the format for database stored dates.
*
* @return string
*/
public function getDateFormat(): string
{
return $this->dateFormat ?? 'Y-m-d H:i:s';
}
/**
* Get the database connection for the model.
*/
public function getConnection(): void
{
throw new LogicException(static::class . ' is not an Eloquent Model; Database Connection is not available.');
}
/**
* Dynamically retrieve attributes on the model.
*
* @param string $key
* @return mixed
*/
public function __get(string $key)
{
return $this->getAttribute($key);
}
/**
* Dynamically set attributes on the model.
*
* @param string $key
* @param mixed $value
*/
public function __set(string $key, $value): void
{
$this->setAttribute($key, $value);
}
/**
* Determine if an attribute or relation exists on the model.
*
* @param string $key
* @return bool
*/
public function __isset(string $key): bool
{
return $this->getAttribute($key) !== null;
}
/**
* Unset an attribute on the model.
*
* @param string $key
*/
public function __unset(string $key): void
{
unset($this->attributes[$key]);
}
/**
* Convert the model instance to an array.
*
* @return array
*/
public function toArray(): array
{
return array_replace($this->attributesToArray(), $this->relationsToArray());
}
}
機能制約
- データベース絡む系は全部無し
- リレーションは手動で
setRelation()
するのみ
- リレーションは手動で
- APIレスポンスとしてそのまま返すことも無いと思うのでインタフェースは
Jsonable
とかは実装せずArrayable
のみ-
ArrayAccess
も基本使わないので無し
-
-
existing()
で外部 API からの取得結果レスポンスからのインスタンス生成を想定 -
apply()
で外部 API からの更新結果レスポンスをインスタンスに反映することを想定