PHPackages                             yuptogun/dootong - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. yuptogun/dootong

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

yuptogun/dootong
================

A simple abstract dynamic pseudo-DTO model for PHP DDD

111[5 issues](https://github.com/yuptogun/dootong/issues)PHP

Since Apr 4Pushed 3y ago1 watchersCompare

[ Source](https://github.com/yuptogun/dootong)[ Packagist](https://packagist.org/packages/yuptogun/dootong)[ RSS](/packages/yuptogun-dootong/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependenciesVersions (1)Used By (0)

dootong
=======

[](#dootong)

Your painkiller for writing DTOs in PHP.

> "headache" in Korean is "두통", which is Romanized into DooTOng.

Quick Start
-----------

[](#quick-start)

```
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');
```

Core Concepts
-------------

[](#core-concepts)

### One DTO, Any Repositories

[](#one-dto-any-repositories)

A `Dootong` is an entity represented as a set of attributes and their handler methods.
A `Headache` is a repository that can give/save entities.
Any `Dootong` can "suffer from" any types of `Headache`, as long as they get along with each other.

```
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
        ]);
}
```

### For the real world problems

[](#for-the-real-world-problems)

The real world queries are inevitably messy.
This is the cause of your daily headache when you have to model them into ORMs.

```
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;
```

Have `Dootong` instead. Define the cause of your headache once, get diagnosed and prescribed.
And then everything starts working.

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

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

How to contribute
-----------------

[](#how-to-contribute)

Everything can be improved, including this README.

### Unit test

[](#unit-test)

```
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
```

###  Health Score

10

—

LowBetter than 0% of packages

Maintenance0

Infrequent updates — may be unmaintained

Popularity7

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity22

Early-stage or recently created project

 Bus Factor1

Top contributor holds 76.6% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://www.gravatar.com/avatar/32823e281b0b5d97347801f5e6396154a0a68a54797c5dc86cbbaa2db7a481e3?d=identicon)[yuptogun](/maintainers/yuptogun)

---

Top Contributors

[![yuptogun](https://avatars.githubusercontent.com/u/6017662?v=4)](https://github.com/yuptogun "yuptogun (36 commits)")[![nntuple-eojin](https://avatars.githubusercontent.com/u/97928417?v=4)](https://github.com/nntuple-eojin "nntuple-eojin (11 commits)")

### Embed Badge

![Health badge](/badges/yuptogun-dootong/health.svg)

```
[![Health](https://phpackages.com/badges/yuptogun-dootong/health.svg)](https://phpackages.com/packages/yuptogun-dootong)
```

###  Alternatives

[wa72/htmlpagedom

jQuery-inspired DOM manipulation extension for Symfony's Crawler

3383.9M34](/packages/wa72-htmlpagedom)[symfony/requirements-checker

Check Symfony requirements and give recommendations

2014.7M29](/packages/symfony-requirements-checker)[symfony/polyfill-intl-messageformatter

Symfony polyfill for intl's MessageFormatter class and related functions

393.9M21](/packages/symfony-polyfill-intl-messageformatter)[nosh2/nosh2

NOSH ChartingSystem.

771.2k](/packages/nosh2-nosh2)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
