PHP code example of gzhegow / orm

1. Go to this page and download the library: Download gzhegow/orm library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

gzhegow / orm example snippets




// > настраиваем PHP
\Gzhegow\Lib\Lib::entrypoint()
    ->setAllRecommended()
    //
    ->setCustomDirRoot(__DIR__ . '/..')
    //
    ->useAll()
    //
    ->lock()
;


// > добавляем несколько функций для тестирования
$ffn = new class {
    function root() : string
    {
        return realpath(__DIR__ . '/..');
    }


    function value_array($value, ?int $maxLevel = null, array $options = []) : string
    {
        return \Gzhegow\Lib\Lib::debug()->dump_value_array($value, $maxLevel, $options);
    }

    function value_array_multiline($value, ?int $maxLevel = null, array $options = []) : string
    {
        return \Gzhegow\Lib\Lib::debug()->dump_value_array_multiline($value, $maxLevel, $options);
    }


    function values($separator = null, ...$values) : string
    {
        return \Gzhegow\Lib\Lib::debug()->dump_values([], $separator, ...$values);
    }


    function print(...$values) : void
    {
        echo $this->values(' | ', ...$values) . PHP_EOL;
    }

    function print_array($value, ?int $maxLevel = null, array $options = []) : void
    {
        echo $this->value_array($value, $maxLevel, $options) . PHP_EOL;
    }

    function print_array_multiline($value, ?int $maxLevel = null, array $options = []) : void
    {
        echo $this->value_array_multiline($value, $maxLevel, $options) . PHP_EOL;
    }


    function test(\Closure $fn, array $args = []) : \Gzhegow\Lib\Modules\Test\TestCase
    {
        $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);

        return \Gzhegow\Lib\Lib::test()->newTestCase()
            ->fn($fn, $args)
            ->trace($trace)
        ;
    }
};



// >>> ЗАПУСКАЕМ!

// > сначала всегда фабрика
$factory = new \Gzhegow\Orm\Core\OrmFactory();

// > создаем сборщик
$builder = $factory->newBuilder();
$builder
    ->defaultStringLength(150)
    //
    // > добавить соединения с БД
    ->fnInit(
        static function ($eloquent) {
            $eloquent->addConnection(
                [
                    'driver' => 'mysql',

                    'host' => 'localhost',
                    'port' => 3306,

                    'username' => 'root',
                    'password' => '',

                    'database' => 'test',

                    'charset'   => 'utf8mb4',
                    'collation' => 'utf8mb4_unicode_ci',

                    'options' => [
                        // > always throw an exception if any error occured
                        \PDO::ATTR_ERRMODE           => \PDO::ERRMODE_EXCEPTION,
                        //
                        // > calculate $pdo->prepare() on PHP level instead of sending it to MySQL as is
                        \PDO::ATTR_EMULATE_PREPARES  => true,
                        //
                        // > since (PHP_VERSION_ID > 80100) mysql integers return integer
                        // > setting ATTR_STRINGIFY_FETCHES flag to TRUE forces returning numeric string
                        \PDO::ATTR_STRINGIFY_FETCHES => true,
                    ],
                ],
                $connName = 'default'
            );
        }
    )
    // //
    // // > выполнить сразу после инициализации Eloquent
    // ->fnBoot(
    //     static function ($eloquent) {
    //         //
    //     }
    // )
    // //
    // // > включаем логирование запросов
    // ->fnLog(
    //     static function ($query) use ($ffn) {
    //         $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 7);
    //         $trace = array_slice($trace, 6);
    //
    //         $sql = preg_replace('~\s+~', ' ', trim($query->sql));
    //         $bindings = $query->bindings;
    //
    //         $files = [];
    //         foreach ( $trace as $item ) {
    //             $traceFile = $item[ 'file' ] ?? '';
    //             $traceLine = $item[ 'line' ] ?? '';
    //
    //             if (! $traceFile) continue;
    //
    //             // > таким образом можно фильтровать список файлов при дебаге, в каком запросе ошибка
    //             // if (false !== strpos($traceFile, '/vendor/')) continue;
    //
    //             $files[] = "{$traceFile}: $traceLine";
    //         }
    //
    //         $dump = [
    //             'sql'      => $sql,
    //             'bindings' => $bindings,
    //             'files'    => $files,
    //         ];
    //
    //         $ffn->print_array_multiline($dump);
    //     }
    // )
;

// > создаем фасад
$facade = $builder->make();

// > устанавливаем фасад
\Gzhegow\Orm\Core\Orm::setFacade($facade);


$eloquent = $facade->getEloquent();
$conn = $eloquent->getConnection();
$schema = $eloquent->getSchemaBuilder($conn);

$modelClassDemoBar = \Gzhegow\Orm\Demo\Model\DemoBarModel::class;
$modelClassDemoBaz = \Gzhegow\Orm\Demo\Model\DemoBazModel::class;
$modelClassDemoFoo = \Gzhegow\Orm\Demo\Model\DemoFooModel::class;
$modelClassDemoImage = \Gzhegow\Orm\Demo\Model\DemoImageModel::class;
$modelClassDemoPost = \Gzhegow\Orm\Demo\Model\DemoPostModel::class;
$modelClassDemoTag = \Gzhegow\Orm\Demo\Model\DemoTagModel::class;
$modelClassDemoUser = \Gzhegow\Orm\Demo\Model\DemoUserModel::class;

$tableDemoBar = $modelClassDemoBar::table();
$tableDemoBaz = $modelClassDemoBaz::table();
$tableDemoFoo = $modelClassDemoFoo::table();
$tableDemoImage = $modelClassDemoImage::table();
$tableDemoPost = $modelClassDemoUser::table();
$tableDemoTag = $modelClassDemoTag::table();
$tableDemoUser = $modelClassDemoPost::table();
$tableTaggable = $modelClassDemoTag::tableMorphedByMany('taggable');


// > удаляем таблицы с прошлого раза
$schema->disableForeignKeyConstraints();
$schema->dropAllTables();
$schema->enableForeignKeyConstraints();


// > создаем таблицы поновой
$schema->create(
    $tableDemoFoo,
    static function (\Gzhegow\Orm\Package\Illuminate\Database\Schema\EloquentSchemaBlueprint $blueprint) {
        $blueprint->bigIncrements('id');
        //
        $blueprint->string('name')->nullable();
    }
);

$schema->create(
    $tableDemoBar,
    static function (\Gzhegow\Orm\Package\Illuminate\Database\Schema\EloquentSchemaBlueprint $blueprint) use (
        $tableDemoFoo
    ) {
        $blueprint->bigIncrements('id');
        //
        $blueprint->unsignedBigInteger($tableDemoFoo . '_id')->nullable();
        //
        $blueprint->string('name')->nullable();

        $blueprint
            ->foreign($tableDemoFoo . '_id')
            ->references('id')
            ->on($tableDemoFoo)
            ->onUpdate('CASCADE')
            ->onDelete('CASCADE')
        ;
    });

$schema->create(
    $tableDemoBaz,
    static function (\Gzhegow\Orm\Package\Illuminate\Database\Schema\EloquentSchemaBlueprint $blueprint) use (
        $tableDemoBar
    ) {
        $blueprint->bigIncrements('id');
        //
        $blueprint->unsignedBigInteger($tableDemoBar . '_id')->nullable();
        //
        $blueprint->string('name')->nullable();

        $blueprint
            ->foreign($tableDemoBar . '_id')
            ->references('id')
            ->on($tableDemoBar)
            ->onUpdate('CASCADE')
            ->onDelete('CASCADE')
        ;
    }
);

$schema->create(
    $tableDemoImage,
    static function (\Gzhegow\Orm\Package\Illuminate\Database\Schema\EloquentSchemaBlueprint $blueprint) use (
        $tableDemoImage
    ) {
        $blueprint->bigIncrements('id');
        //
        $blueprint->nullableMorphs('imageable');
        //
        $blueprint->string('name')->nullable();
    }
);

$schema->create(
    $tableDemoPost,
    static function (\Gzhegow\Orm\Package\Illuminate\Database\Schema\EloquentSchemaBlueprint $blueprint) use (
        $tableDemoPost
    ) {
        $blueprint->bigIncrements('id');
        //
        $blueprint->string('name')->nullable();
    }
);

$schema->create(
    $tableDemoUser,
    static function (\Gzhegow\Orm\Package\Illuminate\Database\Schema\EloquentSchemaBlueprint $blueprint) use (
        $tableDemoUser
    ) {
        $blueprint->bigIncrements('id');
        //
        $blueprint->string('name')->nullable();
    }
);

$schema->create(
    $tableDemoTag,
    static function (\Gzhegow\Orm\Package\Illuminate\Database\Schema\EloquentSchemaBlueprint $blueprint) use (
        $tableDemoTag
    ) {
        $blueprint->bigIncrements('id');
        //
        $blueprint->string('name')->nullable();
    }
);

$schema->create(
    $tableTaggable,
    static function (\Gzhegow\Orm\Package\Illuminate\Database\Schema\EloquentSchemaBlueprint $blueprint) use (
        $tableTaggable
    ) {
        $blueprint->bigInteger('tag_id')->nullable()->unsigned();
        //
        $blueprint->nullableMorphs('taggable');
    }
);



// >>> ТЕСТЫ

// > TEST
// > используем рекурсивное сохранение для того, чтобы сохранить модели вместе со связями
$fn = function () use (
    $eloquent,
    $schema,
    $ffn
) {
    $ffn->print('[ TEST 1 ]');
    echo PHP_EOL;


    $modelClassDemoFoo = \Gzhegow\Orm\Demo\Model\DemoFooModel::class;
    $modelClassDemoBar = \Gzhegow\Orm\Demo\Model\DemoBarModel::class;
    $modelClassDemoBaz = \Gzhegow\Orm\Demo\Model\DemoBazModel::class;

    $schema->disableForeignKeyConstraints();
    $modelClassDemoFoo::query()->truncate();
    $modelClassDemoBar::query()->truncate();
    $modelClassDemoBaz::query()->truncate();
    $schema->enableForeignKeyConstraints();


    $foo1 = $modelClassDemoFoo::new();
    $foo1->name = 'foo1';
    $bar1 = $modelClassDemoBar::new();
    $bar1->name = 'bar1';
    $baz1 = $modelClassDemoBaz::new();
    $baz1->name = 'baz1';

    $foo2 = $modelClassDemoFoo::new();
    $foo2->name = 'foo2';
    $bar2 = $modelClassDemoBar::new();
    $bar2->name = 'bar2';
    $baz2 = $modelClassDemoBaz::new();
    $baz2->name = 'baz2';


    $bar1->_demoFoo = $foo1;
    $baz1->_demoBar = $bar1;

    $baz1->saveRecursive();


    $bar2->_demoFoo = $foo2;
    $baz2->_demoBar = $bar2;
    $bar2->_demoBazs[] = $baz2;
    $foo2->_demoBars[] = $bar2;

    $foo2->saveRecursive();


    $fooCollection = $modelClassDemoFoo::query()->get([ '*' ]);
    $barCollection = $modelClassDemoBar::query()->get([ '*' ]);
    $bazCollection = $modelClassDemoBaz::query()->get([ '*' ]);

    $ffn->print($fooCollection);
    $ffn->print($fooCollection[0]->id, $fooCollection[1]->id);

    $ffn->print($barCollection);
    $ffn->print($barCollection[0]->id, $barCollection[0]->demo_foo_id);
    $ffn->print($barCollection[1]->id, $barCollection[1]->demo_foo_id);

    $ffn->print($bazCollection);
    $ffn->print($bazCollection[0]->id, $bazCollection[0]->demo_bar_id);
    $ffn->print($bazCollection[1]->id, $bazCollection[1]->demo_bar_id);
};
$test = $ffn->test($fn);
$test->expectStdout('
"[ TEST 1 ]"

{ object(countable(2) iterable stringable) # Gzhegow\Orm\Package\Illuminate\Database\Eloquent\EloquentModelCollection }
"1" | "2"
{ object(countable(2) iterable stringable) # Gzhegow\Orm\Package\Illuminate\Database\Eloquent\EloquentModelCollection }
"1" | "1"
"2" | "2"
{ object(countable(2) iterable stringable) # Gzhegow\Orm\Package\Illuminate\Database\Eloquent\EloquentModelCollection }
"1" | "1"
"2" | "2"
');
$test->run();


// > TEST
// > используем Persistence для сохранения ранее созданных моделей
// > это нужно, чтобы уменьшить время транзакции - сохранение делаем в конце бизнес-действия
$fn = function () use (
    $eloquent,
    $schema,
    $ffn
) {
    $ffn->print('[ TEST 2 ]');
    echo PHP_EOL;


    $modelClassDemoFoo = \Gzhegow\Orm\Demo\Model\DemoFooModel::class;
    $modelClassDemoBar = \Gzhegow\Orm\Demo\Model\DemoBarModel::class;
    $modelClassDemoBaz = \Gzhegow\Orm\Demo\Model\DemoBazModel::class;

    $schema->disableForeignKeyConstraints();
    $modelClassDemoFoo::query()->truncate();
    $modelClassDemoBar::query()->truncate();
    $modelClassDemoBaz::query()->truncate();
    $schema->enableForeignKeyConstraints();


    $foo3 = $modelClassDemoFoo::new();
    $foo3->name = 'foo3';
    $bar3 = $modelClassDemoBar::new();
    $bar3->name = 'bar3';
    $baz3 = $modelClassDemoBaz::new();
    $baz3->name = 'baz3';

    $foo4 = $modelClassDemoFoo::new();
    $foo4->name = 'foo4';
    $bar4 = $modelClassDemoBar::new();
    $bar4->name = 'bar4';
    $baz4 = $modelClassDemoBaz::new();
    $baz4->name = 'baz4';


    $bar3->_demoFoo = $foo3;
    $baz3->_demoBar = $bar3;

    $baz3->persistForSaveRecursive();


    $bar4->_demoFoo = $foo4;
    $baz4->_demoBar = $bar4;
    $bar4->_demoBazs[] = $baz4;
    $foo4->_demoBars[] = $bar4;

    $foo4->persistForSaveRecursive();


    \Gzhegow\Orm\Core\Orm::persistence()->flush();


    $fooCollection = $modelClassDemoFoo::query()->get([ '*' ]);
    $barCollection = $modelClassDemoBar::query()->get([ '*' ]);
    $bazCollection = $modelClassDemoBaz::query()->get([ '*' ]);

    $ffn->print($fooCollection);
    $ffn->print($fooCollection[0]->id, $fooCollection[1]->id);

    $ffn->print($barCollection);
    $ffn->print($barCollection[0]->id, $barCollection[0]->demo_foo_id);
    $ffn->print($barCollection[1]->id, $barCollection[1]->demo_foo_id);

    $ffn->print($bazCollection);
    $ffn->print($bazCollection[0]->id, $bazCollection[0]->demo_bar_id);
    $ffn->print($bazCollection[1]->id, $bazCollection[1]->demo_bar_id);
};
$test = $ffn->test($fn);
$test->expectStdout('
"[ TEST 2 ]"

{ object(countable(2) iterable stringable) # Gzhegow\Orm\Package\Illuminate\Database\Eloquent\EloquentModelCollection }
"1" | "2"
{ object(countable(2) iterable stringable) # Gzhegow\Orm\Package\Illuminate\Database\Eloquent\EloquentModelCollection }
"1" | "1"
"2" | "2"
{ object(countable(2) iterable stringable) # Gzhegow\Orm\Package\Illuminate\Database\Eloquent\EloquentModelCollection }
"1" | "1"
"2" | "2"
');
$test->run();


// > TEST
// > тестирование связей (для примера взят Morph), у которых в этом пакете изменился интерфейс создания
$fn = function () use (
    $eloquent,
    $schema,
    $ffn
) {
    $ffn->print('[ TEST 3 ]');
    echo PHP_EOL;


    $modelClassDemoPost = \Gzhegow\Orm\Demo\Model\DemoPostModel::class;
    $modelClassDemoUser = \Gzhegow\Orm\Demo\Model\DemoUserModel::class;
    $modelClassDemoImage = \Gzhegow\Orm\Demo\Model\DemoImageModel::class;
    $modelClassDemoTag = \Gzhegow\Orm\Demo\Model\DemoTagModel::class;

    $schema->disableForeignKeyConstraints();
    $modelClassDemoPost::query()->truncate();
    $modelClassDemoUser::query()->truncate();
    $modelClassDemoImage::query()->truncate();
    $modelClassDemoTag::query()->truncate();
    $schema->enableForeignKeyConstraints();


    $post1 = $modelClassDemoPost::new();
    $post1->name = 'post1';

    $user1 = $modelClassDemoUser::new();
    $user1->name = 'user1';

    $image1 = $modelClassDemoImage::new();
    $image1->name = 'image1';

    $image2 = $modelClassDemoImage::new();
    $image2->name = 'image2';

    $image1->_imageable = $post1;
    $image2->_imageable = $user1;

    $post1->_demoImages[] = $image1;

    $user1->_demoImages[] = $image2;


    $image1->persistForSaveRecursive();
    $image2->persistForSaveRecursive();

    \Gzhegow\Orm\Core\Orm::persistence()->flush();


    $imageQuery = $image1::query()
        ->addColumns($image1->getMorphKeys('imageable'))
        ->with(
            $post1::relationDot()([ $image1, '_imageable' ])()
        )
    ;
    $postQuery = $post1::query()
        ->with(
            $post1::relationDot()([ $post1, '_demoImages' ])()
        )
    ;
    $userQuery = $user1::query()
        ->with(
            $user1::relationDot()([ $user1, '_demoImages' ])()
        )
    ;

    $imageCollection = $modelClassDemoImage::get($imageQuery);
    $postCollection = $modelClassDemoPost::get($postQuery);
    $userCollection = $modelClassDemoUser::get($userQuery);

    $ffn->print($imageCollection);
    $ffn->print($imageCollection[0], $imageCollection[0]->_imageable);
    echo PHP_EOL;

    $ffn->print($postCollection);
    $ffn->print($postCollection[0], $postCollection[0]->_demoImages[0]);
    echo PHP_EOL;

    $ffn->print($userCollection);
    $ffn->print($userCollection[0], $userCollection[0]->_demoImages[0]);
    echo PHP_EOL;


    $post2 = $modelClassDemoPost::new();
    $post2->name = 'post2';

    $user2 = $modelClassDemoUser::new();
    $user2->name = 'user2';

    $tag1 = $modelClassDemoTag::new();
    $tag1->name = 'tag1';

    $tag2 = $modelClassDemoTag::new();
    $tag2->name = 'tag2';


    $post2->persistForSave();
    $post2->_demoTags()->persistForSaveMany([
        $tag1,
        $tag2,
    ]);

    $user2->persistForSave();
    $user2->_demoTags()->persistForSaveMany([
        $tag1,
        $tag2,
    ]);

    \Gzhegow\Orm\Core\Orm::persistence()->flush();


    $tagQuery = $modelClassDemoTag::query()
        ->with([
            $modelClassDemoTag::relationDot()([ $modelClassDemoTag, '_demoPosts' ])(),
            $modelClassDemoTag::relationDot()([ $modelClassDemoTag, '_demoUsers' ])(),
        ])
    ;
    $postQuery = $post2::query()
        ->with(
            $post2::relationDot()([ $post2, '_demoTags' ])()
        )
    ;
    $userQuery = $user2::query()
        ->with(
            $user2::relationDot()([ $user2, '_demoTags' ])()
        )
    ;

    $tagCollection = $modelClassDemoTag::get($tagQuery);
    $postCollection = $modelClassDemoPost::get($postQuery);
    $userCollection = $modelClassDemoUser::get($userQuery);

    $ffn->print($tagCollection);
    $ffn->print($tagCollection[0], $tagCollection[0]->_demoPosts[0], $tagCollection[0]->_demoUsers[0]);
    $ffn->print($tagCollection[1], $tagCollection[1]->_demoPosts[0], $tagCollection[1]->_demoUsers[0]);
    echo PHP_EOL;

    $ffn->print($postCollection);
    $ffn->print($postCollection[1], $postCollection[1]->_demoTags[0], $postCollection[1]->_demoTags[1]);
    echo PHP_EOL;

    $ffn->print($userCollection);
    $ffn->print($userCollection[1], $userCollection[1]->_demoTags[0], $userCollection[1]->_demoTags[1]);
};
$test = $ffn->test($fn);
$test->expectStdout('
"[ TEST 3 ]"

{ object(countable(2) iterable stringable) # Gzhegow\Orm\Package\Illuminate\Database\Eloquent\EloquentModelCollection }
{ object(stringable) # Gzhegow\Orm\Demo\Model\DemoImageModel } | { object(stringable) # Gzhegow\Orm\Demo\Model\DemoPostModel }

{ object(countable(1) iterable stringable) # Gzhegow\Orm\Package\Illuminate\Database\Eloquent\EloquentModelCollection }
{ object(stringable) # Gzhegow\Orm\Demo\Model\DemoPostModel } | { object(stringable) # Gzhegow\Orm\Demo\Model\DemoImageModel }

{ object(countable(1) iterable stringable) # Gzhegow\Orm\Package\Illuminate\Database\Eloquent\EloquentModelCollection }
{ object(stringable) # Gzhegow\Orm\Demo\Model\DemoUserModel } | { object(stringable) # Gzhegow\Orm\Demo\Model\DemoImageModel }

{ object(countable(2) iterable stringable) # Gzhegow\Orm\Package\Illuminate\Database\Eloquent\EloquentModelCollection }
{ object(stringable) # Gzhegow\Orm\Demo\Model\DemoTagModel } | { object(stringable) # Gzhegow\Orm\Demo\Model\DemoPostModel } | { object(stringable) # Gzhegow\Orm\Demo\Model\DemoUserModel }
{ object(stringable) # Gzhegow\Orm\Demo\Model\DemoTagModel } | { object(stringable) # Gzhegow\Orm\Demo\Model\DemoPostModel } | { object(stringable) # Gzhegow\Orm\Demo\Model\DemoUserModel }

{ object(countable(2) iterable stringable) # Gzhegow\Orm\Package\Illuminate\Database\Eloquent\EloquentModelCollection }
{ object(stringable) # Gzhegow\Orm\Demo\Model\DemoPostModel } | { object(stringable) # Gzhegow\Orm\Demo\Model\DemoTagModel } | { object(stringable) # Gzhegow\Orm\Demo\Model\DemoTagModel }

{ object(countable(2) iterable stringable) # Gzhegow\Orm\Package\Illuminate\Database\Eloquent\EloquentModelCollection }
{ object(stringable) # Gzhegow\Orm\Demo\Model\DemoUserModel } | { object(stringable) # Gzhegow\Orm\Demo\Model\DemoTagModel } | { object(stringable) # Gzhegow\Orm\Demo\Model\DemoTagModel }
');
$test->run();


// > TEST
// > можно подсчитать количество записей в таблице используя EXPLAIN, к сожалению, будет показано число строк, которое придется обработать, а не число строк по результатам запроса
// > но иногда этого достаточно, особенно если запрос покрыт должным числом индексов, чтобы отобразить "Всего: ~100 страниц"
$fn = function () use (
    $eloquent,
    $schema,
    $ffn
) {
    $ffn->print('[ TEST 4 ]');
    echo PHP_EOL;


    $modelClassDemoTag = \Gzhegow\Orm\Demo\Model\DemoTagModel::class;

    $schema->disableForeignKeyConstraints();
    $modelClassDemoTag::query()->truncate();
    $schema->enableForeignKeyConstraints();

    for ( $i = 0; $i < 100; $i++ ) {
        $tag = $modelClassDemoTag::new();
        $tag->name = 'tag' . $i;
        $tag->save();
    }


    $query = $modelClassDemoTag::query()->where('name', 'tag77');
    $ffn->print($cnt = $query->count(), $cnt === 1);

    $cnt = $query->countExplain();
    $ffn->print($cnt > 1, $cnt <= 100);
};
$test = $ffn->test($fn);
$test->expectStdout('
"[ TEST 4 ]"

1 | TRUE
TRUE | TRUE
');
$test->run();


// > TEST
// > используем механизм Chunk, чтобы считать данные из таблиц
// > на базе механизма работает и пагинация, предлагается два варианта - нативный SQL LIMIT/OFFSET и COLUMN(>|>=|<|<=)VALUE
$fn = function () use (
    $eloquent,
    $schema,
    $ffn
) {
    $ffn->print('[ TEST 5 ]');
    echo PHP_EOL;


    $modelClassDemoTag = \Gzhegow\Orm\Demo\Model\DemoTagModel::class;

    $schema->disableForeignKeyConstraints();
    $modelClassDemoTag::query()->truncate();
    $schema->enableForeignKeyConstraints();

    for ( $i = 0; $i < 100; $i++ ) {
        $tag = $modelClassDemoTag::new();
        $tag->name = 'tag' . $i;
        $tag->save();
    }


    $ffn->print('chunkModelNativeForeach');
    $builder = $modelClassDemoTag::chunks();
    $builder->chunksModelNativeForeach(
        $limitChunk = 25, $limit = null, $offset = null
    );
    foreach ( $builder->chunksForeach() as $chunk ) {
        $ffn->print($chunk);
    }
    echo PHP_EOL;


    $ffn->print('chunkModelAfterForeach');
    $builder = $modelClassDemoTag::chunks();
    $builder = $builder->chunksModelAfterForeach(
        $limitChunk = 25, $limit = null,
        $offsetColumn = 'id', $offsetOperator = '>', $offsetValue = 1, $

Это сильно упрощает чтение кода, разделяя связи и свойства, а также ускоряет проверку при считывании свойства на предмет "нужно ли интерпретировать свойство как связь"

Eloquent::setRelationPrefix('_');

/**
 * @property int            $id
 * @property string         $name
 *
 * @property int            $demo_foo_id
 * @property DemoFooModel   $_demoFoo
 */
abstract class AbstractModel extends \Gzhegow\Orm\Package\Illuminate\Database\Eloquent\EloquentModel
{
}

/**
 * @property int            $id
 * @property string         $name
 *
 * @property int            $demo_foo_id
 * @property DemoFooModel   $_demoFoo
 */
class MyModel extends AbstractModel
{
    protected static function relationClasses() : array
    {
        return [
            '_demoFoo'  => BelongsTo::class,
        ];
    }

    public function _demoFoo() : BelongsTo
    {
        return $this->relation()
            ->belongsTo(
                __FUNCTION__,
                DemoFooModel::class
            )
        ;
    }
}

Это делается с помощью \Generator, распределяя по времени нагрузку на обмен данных между базой и приложением

$builder = MyModel::chunks();
$builder->chunksModelNativeForeach(
    $limitChunk = 25, $limit = null, $offset = null
);
foreach ( $builder->chunksForeach() as $chunk ) {
    _dump($chunk);
}

$builder = MyModel::chunks();
$builder
    // ->setTotalItems(100)
    // ->setTotalPages(8)
    // ->withSelectCountNative()
    // ->withSelectCountExplain()
    ->paginatePdoNativeForeach(
        $perPage = 13, $page = 7, $pagesDelta = 2,
        $offset = null
    )
;
$result = $builder->paginateResult();

// НЕ ВЕРНО:
$query->with([
    '_relationName._relationNameChild',
    '_relationName._relationNameChild',
    '_relationName._relationNameChild',
]);
$model->load([
    '_relationName._relationNameChild',
    '_relationName._relationNameChild',
    '_relationName._relationNameChild',
]);
$collection->load([
    '_relationName._relationNameChild',
    '_relationName._relationNameChild',
    '_relationName._relationNameChild',
]);

// ВЕРНО:
$query->with([
    Orm::relationDot()([ ModelClass::class, '_relationName' ])([ Model2Class::class, '_relationNameChild' ])(),
    Orm::relationDot()([ ModelClass::class, '_relationName' ])([ Model2Class::class, '_relationNameChild' ])(),
    Orm::relationDot()([ ModelClass::class, '_relationName' ])([ Model2Class::class, '_relationNameChild' ])(),
]);
$model->load([
    Orm::relationDot()([ ModelClass::class, '_relationName' ])([ Model2Class::class, '_relationNameChild' ])(),
    Orm::relationDot()([ ModelClass::class, '_relationName' ])([ Model2Class::class, '_relationNameChild' ])(),
    Orm::relationDot()([ ModelClass::class, '_relationName' ])([ Model2Class::class, '_relationNameChild' ])(),
]);
$collection->load([
    Orm::relationDot()([ ModelClass::class, '_relationName' ])([ Model2Class::class, '_relationNameChild' ])(),
    Orm::relationDot()([ ModelClass::class, '_relationName' ])([ Model2Class::class, '_relationNameChild' ])(),
    Orm::relationDot()([ ModelClass::class, '_relationName' ])([ Model2Class::class, '_relationNameChild' ])(),
]);

php test.php