Resource
URIに対応するリソースは、状態を決めて返す。
final class Profile extends ResourceObject
{
public function onGet(int $id): static
{
$this->body = $this->profileQuery->item($id);
return $this;
}
}Code
BEAR.Sundayのコードは、リソースが中心です。入力、依存、リンク、横断的処理が見える場所に残るため、読み手はアプリケーションの意味をたどれます。
Resource
final class Profile extends ResourceObject
{
public function onGet(int $id): static
{
$this->body = $this->profileQuery->item($id);
return $this;
}
}Dependency Injection
public function __construct(
private readonly ProfileQuery $profileQuery,
private readonly ClockInterface $clock,
) {
}AOP
#[Transactional]
#[Loggable]
public function onPost(string $name): static
{
$this->body = $this->command->create($name);
return $this;
}Hypermedia
#[Link(rel: 'orders', href: 'app://self/orders{?id}')]
#[Embed(rel: 'profile', src: 'app://self/profile{?id}')]
public function onGet(int $id): static
{
return $this;
}CacheableResponse
use BEAR\RepositoryModule\Annotation\CacheableResponse;
#[CacheableResponse]
public function onGet(string $id): static
{
$this->body = $this->blog->entry($id);
return $this;
}DonutCache
#[DonutCache]
#[Embed(rel: 'comment', src: 'page://self/blog/comment')]
public function onGet(int $id): static
{
$this->body += ['article' => '...'];
return $this;
}CLI
use BEAR\Cli\Attribute\Cli;
use BEAR\Cli\Attribute\Option;
#[Cli(name: 'greet', description: '多言語で挨拶', output: 'greeting')]
public function onGet(
#[Option(shortName: 'n')] string $name,
#[Option(shortName: 'l')] string $lang = 'en',
): static {
// この onGet が、独立した greet コマンドになる
// --help も --name/-n も、宣言から自動で生成される
return $this;
}SQL
interface OrderRepositoryInterface
{
// 戻り値の型が意図:不変ドメインオブジェクト
#[DbQuery('order_item', factory: OrderFactory::class)]
public function getOrder(string $id): Order;
// 型のある引数。現在時刻はDIが束縛。戻り値の型 = 更新行数
#[DbQuery('order_close')]
public function close(string $id, DateTimeInterface $at): AffectedRows;
}What this enables
リソースが状態を持ち、表現や転送は外側へ分かれる。依存は注入され、 横断的関心はAOPへ分かれる。この単純な分離が、テストと拡張の土台になります。
Start with one resource