• Час читання ~1 хв
  • 03.10.2022

Laravel Eloquent — це одна з найпотужніших і найдивовижніших функцій сучасного фреймворку. Від трансляції даних до об’єктів і класів значень, захист бази даних за допомогою полів, які можна заповнити, транзакцій, областей, глобальних областей і зв’язків. Eloquent дає вам змогу досягти успіху в будь-якій справі з базою даних.

Початок роботи з Eloquent іноді може лякати, оскільки він може зробити так багато, що ви ніколи не знаєте, з чого почати. У цьому підручнику я зосереджуся на тому, що я вважаю одним із важливих аспектів будь-якої програми – записі в базу даних.

Ви можете писати в базу даних у будь-якій області програми: контролер, завдання, проміжне програмне забезпечення, команда artisan. Який найкращий спосіб обробки записів у базу даних?

Почнемо з простої моделі Eloquent без зв’язків.

final class Post extends Model
{
    protected $fillable = [
        'title',
        'slug',
        'content',
        'published',
    ];
 
    protected $casts = [
        'published' => 'boolean',
    ];
}

У нас є модель Post що представляє публікацію в блозі; він має заголовок, слаг, вміст і логічний прапор, який повідомляє, чи опубліковано його. У цьому прикладі уявімо, що опублікована властивість за замовчуванням має значення true у базі даних. Тепер, для початку, ми сказали Eloquent, що ми хочемо мати можливість заповнювати властивості або стовпці title, slug, content і published. Отже, якщо ми передаємо щось, що не зареєстровано в масиві fillable, буде створено виняток, захищаючи нашу програму від потенційних проблем.

Тепер, коли ми знаємо, які поля можна заповнювати, ми можемо розглянути запис даних до бази даних, будь то створення, оновлення чи видалення. Якщо ваша модель успадковує ознаку SoftDeletes, тоді видалення запису є дією запису, але для цього прикладу я буду простим; видалення є видаленням.

Найімовірніше, ви бачили, особливо в документації, щось на зразок наступного:

Post::create($request->only('title', 'slug', 'content'));

Це те, що я можу стандартизувати Eloquent, у вас є модель, і ви викликаєте статичний метод, щоб створити новий екземпляр, передаючи певний масив із запиту. Цей підхід має переваги; це чисто і просто, і всі це розуміють. Іноді я можу бути дуже самовпевненим розробником. Однак я все одно використовуватиму цей підхід, особливо якщо я перебуваю в режимі прототипування, де більше стосується тестування ідеї, а не створення чогось довгострокового.

Ми можемо зробити цей крок далі, запустивши новий екземпляр конструктора запитів Eloquent на моделі, перш ніж запитувати створення нового екземпляра. Це виглядатиме так:

Post::query()->create($request->only('title', 'slug', 'content'));

Як бачите, це все ще дуже просто і стає все більш стандартизованим способом запуску запитів у Laravel. Однією з найважливіших переваг цього підходу є те, що все після query відповідає контракту Query Builder Contract, який нещодавно було представлено. Через те, як Laravel працює під капотом, ваша IDE не дуже добре розумітиме статичні виклики, оскільки це статичний проксі для методу, який використовує __callStatic замість фактичного статичного методу. На щастя, це не стосується методу query, який є статичним методом моделі Eloquent, яку ви розширюєте.

Існує «старіший» метод побудови вашої моделі для збереження база даних. Однак я вже рідко бачу, як його використовують дуже часто. Однак я згадаю про це для ясності:

$post = new Post();
$post->title = $request->get('title');
$post->slug = $request->get('slug');
$post->content = $request->get('content');
$post->save();

Тут ми будемо будувати модель програмним шляхом, призначаючи значення властивостям і потім зберігаючи її в базі даних. Це було трохи довго, і завжди здавалося, що це занадто багато зусиль. Однак це все ще прийнятний спосіб створення нової моделі, якщо ви віддаєте перевагу цьому.

Наразі ми розглядали три різні підходи до створення нових даних у бази даних. Ми можемо використовувати подібний підхід до оновлення даних у базі даних, статичний виклик update або використання контракту створення запиту query()->where('column', 'value')->update() або, нарешті, програмне встановлення властивості, а потім збереження. Я не буду повторюватися тут, оскільки це майже те саме, що й вище.

Що робити, якщо ми не впевнені, чи запис уже існує? Наприклад, ми хочемо створити або оновити наявну публікацію. У нас буде стовпець, який ми хочемо перевірити з точки зору унікальності - потім ми передаємо масив значень, які ми хочемо створити або оновити залежно від того, чи він існує.

Post::query()->updateOrCreate(
    attributes: ['slug' => $request->get('slug'),
    values: [
        'title' => $request->get('title'),
        'content' => $request->get('content'),
    ],
);

Це має величезні переваги, якщо ви не впевнені, чи існуватиме запис, і я нещодавно реалізував це сам, коли хотів «забезпечити» наявність запису в базі даних, незважаючи ні на що. Наприклад, для соціального входу OAuth 2.0 ви можете прийняти інформацію від постачальника та оновити або створити новий запис перед автентифікацією користувача.

Чи можемо ми зробити крок далі? Які будуть переваги? Ви можете використовувати такий шаблон, як шаблон сховища, щоб фактично «проксі» виклики, які ви надсилали б eloquent через інший клас. У цьому є кілька переваг або, принаймні, вони були раніше, перш ніж Eloquent став тим, чим є сьогодні. Давайте розглянемо приклад:

class PostRepository
{
    private Model $model;
 
    public function __construct()
    {
        $this->model = Post::query();
    }
 
    public function create(array $attributes): Model
    {
        return $this->model->create(
            attributes: $attributes,
        );
    }
}

Якби ми використовували фасад БД або звичайний PDO, тоді, можливо, шаблон сховища дав би нам багато переваг у підтримці узгодженості. Йдемо далі.

У якийсь момент люди вирішили, що гарною ідеєю буде перехід від класу Repository до класу Service. Однак це те саме ... Не будемо вдаватися в це.

Отже, нам потрібен спосіб взаємодії з Eloquent, який не є таким «вбудованим» або процедурним. Кілька років тому я прийняв підхід, який зараз називається «дії». Він схожий на шаблон сховища. Однак кожна взаємодія з Eloquent є окремим класом, а не методом в межах одного класу.

Подивимося на цей приклад, де у нас є спеціальний клас для кожної взаємодії, який називається «дія»:

final class CreateNewPostAction implements CreateNewPostContract
{
    public function handle(array $attributes): Model|Post
    {
        return Post::query()
            ->create(
                attributes: $attributes,
            );
    }
}

Наш клас реалізує контракт, щоб гарно прив’язати його до контейнера, дозволяючи нам вставити це в конструктор і викликати метод обробки з нашими даними, коли це необхідно. Це стає все більш популярним, і багато людей (як і пакунки) почали застосовувати цей підхід, оскільки ви створюєте корисні класи, які добре виконують одну справу, і можуть легко створювати для них дублі тестів. Ще одна перевага полягає в тому, що ми використовуємо інтерфейс; якщо ми колись вирішимо відійти від Eloquent (не знаю, чому ви цього хочете), ми можемо швидко змінити наш код, щоб відобразити це, не шукаючи нічого.

Знову ж таки, підхід досить гарний і, в принципі, не має жодних мінусів. Я згадав, що я досить вибагливий розробник, чи не так? Що ж...

Моя найбільша проблема з "діями" після їхнього так тривалого використання полягає в тому, що ми поміщаємо всі наші інтеграції запису, оновлення та видалення під один капот. Дії недостатньо поділяють речі для мене. Якщо я подумаю про це, у нас є дві різні речі, яких ми хочемо досягти: ми хочемо писати і ми хочемо читати. Це частково відображає інший шаблон проектування під назвою CQRS (Command Query Responsibility Segregation), який я трохи запозичив. У CQRS, як правило, ви використовуєте шину команд і шину запитів для читання та запису даних, як правило, випромінюючи події, які слід зберігати за допомогою джерела подій. Однак іноді це набагато більше роботи, ніж вам потрібно. Не зрозумійте мене неправильно, для такого підходу точно є час і місце, але ви повинні тягнутися до нього лише тоді, коли вам це необхідно – інакше ви переробите своє рішення з найменшої частини.


Тож я розділив дії запису на «Команди», а дії читання — на «Запити», щоб мої взаємодії були розділеними та зосередженими. Давайте подивимося на Command:

Погляньте на це, окрім іменування класу, це те саме, що й дія. Це задумом. Дії — чудовий спосіб запису в базу даних. Я вважаю, що вони занадто швидко переповнюються.

final class CreateNewPost implements CreateNewPostContract
{
    public function handle(array $attributes): Model|Post
    {
        return Post::query()
            ->create(
                attributes: $attributes,
            );
    }
}

Які ще способи ми можемо покращити? Представлення об’єкта передачі домену було б хорошим місцем для початку, оскільки він забезпечує безпеку типів, контекст і послідовність.

Тож ми зараз запроваджуємо безпеку типів у масиві, де раніше покладалися на масиви та сподівалися, що все піде правильно. Так, ми можемо перевіряти досхочу, але об’єкти мають кращу узгодженість.

final class CreateNewPost implements CreateNewPostContract
{
    public function handle(CreatePostRequest $post): Model|Post
    {
        return Post::query()
            ->create(
                attributes: $post->toArray(),
            );
    }
}

Чи можна якось покращити це? Завжди є місце для вдосконалення, але чи потрібно нам це робити? Цей поточний підхід є надійним, безпечним для типу та легким для запам’ятовування. Але що нам робити, якщо таблиця бази даних заблокується до того, як ми зможемо записати, або якщо у нас виникне перерив підключення до мережі, можливо, Cloudflare вийде з ладу в невідповідний момент.

Транзакції бази даних будуть рятуй наші дупи тут. Вони використовуються не так часто, як, мабуть, мали б бути, але це потужний інструмент, який слід незабаром розглянути.

Зрештою ми досягли цього! Я б стрибав від радості, якби побачив подібний код у PR або огляді коду, який мені потрібно було зробити. Однак не думайте, що вам потрібно писати код таким чином. Пам’ятайте, що цілком нормально просто вбудувати static create, якщо це зробить вашу роботу! Важливо робити те, що вам зручно, що зробить вас ефективними, а не те, що інші кажуть, що ви повинні робити в спільноті.

final class CreateNewPost implements CreateNewPostContract
{
    public function handle(CreatePostRequest $post): Model|Post
    {
        return DB::transaction(
            fn() => Post::query()->create(
                attributes: $post->toArray(),
            )
        );
    }
}

Використовуючи підхід, який ми щойно розглянули, ми могли б підійти до читання з бази даних таким же чином. Розберіть проблему, визначте кроки та те, де можна зробити покращення, але завжди запитуйте, чи не зайшли ви занадто далеко. Якщо це здається природним, це, ймовірно, хороший знак.

Як ви підходите до запису в базу даних? Як далеко ви зайшли б у подорожі, а коли занадто далеко? Поділіться з нами своїми думками в Twitter!

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

Про мене

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

Про автора CrazyBoy49z
WORK EXPERIENCE
Контакти
Ukraine, Lutsk
+380979856297