yuptogun/dootong

PHP DDD 的一种简单抽象动态伪 DTO 模型

dev-main 2023-04-04 00:53 UTC

This package is auto-updated.

Last update: 2024-09-04 04:13:21 UTC


README

您在 PHP 中编写 DTO 的止痛药。

在韩语中,“头痛”是“두통”,罗马化为 DooTOng。

快速入门

class User extends Yuptogun\Dootong\Dootong
{
    protected function getAttributes(): array {
        return ['id', 'email', 'username', 'password', 'created_at'];
    }
    protected function getRequiredAttributes(): array {
        return ['email', 'password'];
    }
    protected function getHiddenAttributes(): array {
        return [];
    }
    protected function getAttributeCastings(): array {
        return [
            'id'         => 'increment',
            'password'   => 'password',
            'created_at' => 'datetime',
        ];
    }
}

$user = User::sufferFrom(
    (new MySQL(new PDO('mysql:host=localhost;dbname=test', 'test', 'test')))
        ->diagnose("SELECT * FROM users WHERE email LIKE concat('%', :email)")
        ->prescribe("INSERT INTO users (email, username, pwd) VALUES (:email, :username, :pwd)")
);

/** @var User[] $yahooUsers */
$yahooUsers = $user->get(['email' => '@yahoo.com']);
foreach ($yahooUsers as $u) {

    /** @var int $id type casted */
    $id = $u->id;

    /** @var null $timezone "hidden" */
    $timezone = $u->timezone;
}

/** @var User $newUser */
$newUser = $user->get($user->set([
    'email'    => 'foo@bar.com',
    'username' => 'foo',
    'pwd'      => 'bar',
]));

/** @var null $newUserPassword "password" type basically hidden */
$newUserPassword = $newUser->pwd;

/**
 * "password" type works with comparison method.
 * if you have multiple password type attributes, specify one in the second argument.
 *
 * @var true $newUserPasswordCheck
 */
$newUserPasswordCheck = $newUser->isPassword('bar');

核心概念

一个 DTO,任何仓库

Dootong 是以一组属性及其处理方法表示的实体。
Headache 是一个可以提供/保存实体的仓库。
任何 Dootong 都可以“受”任何类型的 Headache 的影响,只要它们能够相处。

use MyApp\DTO\Order;

$redis = new Yuptogun\Dootong\Types\Redis($config);
$mysql = new Yuptogun\Dootong\Types\MySQL($pdo);
$ordersQueued = Order::setRepository($redis)
    ->diagnose('LRANGE orders 0 10')
    ->get();
foreach ($ordersQueued as $order) {
    Order::sufferFrom($mysql)
        ->prescribe("INSERT INTO orders (user_id, product_id) VALUES (:user_id, :product_id)")
        ->set([
            'user_id'    => $order->user_id,
            'product_id' => $order->product_id
        ]);
}

针对现实世界问题

现实世界的查询不可避免地是杂乱的。
这是您在将它们建模为 ORM 时每天头痛的原因。

SELECT
    a.a_id AS `user_id`,
    max(a.a_name) AS `user_name`,
    ifnull(max(bc.bc_name), '') AS `purchase_name`
FROM a
LEFT JOIN (
    SELECT b.a_id, b.b_name AS bc_name, concat(b.b_name, b.b_id) AS bc_id
    FROM b WHERE b.a_id = a.a_id
    AND b.created_at >= '2021-01-01 00:00:00' AND b.cancelled_at IS NULL
    UNION ALL
    SELECT c.a_id, c.c_name AS bc_name, concat(c.c_name, c.c_id) AS bc_id
    FROM c WHERE c.a_id = a.a_id
    AND c.created_at >= '2021-01-01 00:00:00' AND c.cancelled_at IS NULL
) bc ON bc.a_id = a.a_id
WHERE a.email LIKE concat('%@', :email_domain)
AND bc.bc_name LIKE concat('%', :product_name)
GROUP BY a.a_id, bc.bc_id;

使用 Dootong。一次定义您头痛的原因,得到诊断和处方。
然后一切开始运转。

class PaidUsersSince2021 extends MySQL
{
    protected $getter = THE_QUERY_ABOVE;
}

$paidUsersSince2021 = PaidUser::sufferFrom(new PaidUsersSince2021($pdo))->get([
    'email_domain' => 'google.com',
    'product_name' => 'painkiller 3000',
]);

如何贡献

一切都可以改进,包括这个 README。

单元测试

docker-compose up -d --build
docker run --rm -it -v "$(pwd):/app" -w /app composer install --ignore-platform-reqs
docker run --rm -it -v "$(pwd):/app" -w /app yuptogun/dootong-test-php php ./vendor/bin/phpunit tests