
Повідомлення вищого порядку - наскільки ж це зручно!
13.02.2019 22:54 | Laravel
У Laravel колекції підтримують так звані повідомлення вищого порядку (higher order messages) - шорткати для виконання деяких загальних дій. Розглянемо кілька прикладів, щоб переконатися, наскільки елегантний і зручний інструмент є в нашому розпорядженні.
Для експериментів створимо новий додаток, виконавши в консолі команду:
laravel new larexp
Додамо категорії та продукти:
php artisan make:model -m Category
php artisan make:model -m Product
Відредагуємо файли міграцій. Категорії:
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateCategoriesTable extends Migration
{
public function up()
{
Schema::create('categories', function (Blueprint $table) {
$table->increments('id');
$table->string('title');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('categories');
}
}
Продукти:
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateProductsTable extends Migration
{
public function up()
{
Schema::create('products', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('category_id');
$table->string('title')->unique();
$table->text('description');
$table->boolean('active');
$table->unsignedInteger('price');
$table->timestamps();
$table->foreign('category_id')->references('id')->on('categories')->onDelete('cascade');
});
}
public function down()
{
Schema::dropIfExists('products');
}
}
У двох словах: ми "прив'язали" продукт до категорії, додали назву, опис і ціну (в центах), а також створили поле для визначення активний чи продукт. Відредагуємо файли моделей. Категорія:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Category extends Model
{
protected $fillable = [
'title'
];
public function products()
{
return $this->hasMany(Product::class);
}
}
Продукт:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
protected $fillable = [
'title', 'description', 'price', 'active'
];
public function category()
{
return $this->belongsTo(Category::class);
}
}
Пояснення: ми відразу визначили відносини (один до багатьох, тобто категорія може включати багато продуктів, продукт належить до будь-якої з категорій). Тепер виконаємо міграції:
php artisan migrate
І додамо фабрики для наповнення таблиць фейковими даними:
php artisan make:factory CategoryFactory --model=Category
php artisan make:factory ProductFactory --model=Product
Замінимо вміст файлів-фабрик. CategoryFactory.php
:
<?php
use Faker\Generator as Faker;
$factory->define(App\Category::class, function (Faker $faker) {
return [
'title' => $faker->sentence(rand(1, 3))
];
});
ProductFactory.php
:
<?php
use Faker\Generator as Faker;
$factory->define(App\Product::class, function (Faker $faker) {
return [
'title' => $faker->unique()->sentence(1, 3),
'description' => $faker->paragraph(),
'price' => $faker->numberBetween(500, 5000),
'active' => $faker->boolean()
];
});
В консолі за допомогою тінкера згенеруємо дані - нехай це буде 5 категорій, по 10 продуктів у кожній:
php artisan tinker
>>> factory(App\Category::class, 5)->create()->each(function ($category) {
$category->products()->saveMany(factory(App\Product::class, 10)->make());
});
Виведемо кількість створених продуктів, має бути 50:
>>> App\Product::count()
=> 50
Відмінно, можна переходити до головної теми.
filter
Припустимо, потрібно відфільтрувати колекцію і отримати тільки ті товари, які активні. Звичайна запис виглядає так:
$activeProducts = $products->filter(function ($product) {
return $product->active;
});
Скористаємося повідомленням вищого порядку:
$activeProducts = $products->filter->active;
Результат ідентичний, але наскільки простіше запис? При цьому код все такий же читабельний. Але якщо умови фільтрації складніші і спираються не тільки на значення boolean
поля? Припустимо, в моделі Product
ми визначили метод isActive
з необхідним умовами. Тепер можемо зробити і так:
$activeProducts = $products->filter->isActive();
Даний код "пройдеться" по всіх продуктах і "вибере" тільки ті, на яких метод isActive
буде повертати true
.
map
З колекції продуктів хочемо отримати назви продуктів (на деякий час забудемо про метод pluck
):
$titles = $products->map(function ($product) {
return $product->title;
});
Знаючи про шорткати, можна отримати назви простіше:
$titles = $products->map->title;
Підемо ще далі: маючи колекцію продуктів, потрібно отримати колекцію (унікальних) категорій, до яких належать ці продукти:
$categories = $products->map->category->unique();
або так (унікальність визначатимемо по id
):
$categories = $products->map->category->unique->id;
sum, min, max, avg
Тут все дуже просто:
$sum = $products->sum->price;
$min = $products->min->price;
$max = $products->max->price;
$avg = $products->avg->price;
Потрібна середня ціна активних продуктів? Скомбінуємо методи:
$avg = $products->filter->active->avg->price;
keyBy
Хочемо отримати колекцію, в якій ключами будуть id
елементів? Запросто:
$byKeys = $products->byKey->id;
sortBy, sortByDesc
Сортування за назвою:
$sortedByTitle = $products->sortBy->title;
Сортування за id
в зворотному порядку:
$descSortedById = $products->sortByDesc->id;
each
Додамо в модель Product
метод для активації продукту:
public function activate()
{
$this->update(['active' => true]);
}
Тепер, щоб активувати всі продукти, досить одного рядка:
$products->each->activate();
Можемо і трохи побавитися. У моделі категорії створимо наступний метод:
public function createFakeProducts($count)
{
$this->products()->saveMany(factory(Product::class, $count)->make());
}
Пам'ятайте, як ми генерували продукти? Нагадаю, це виглядало ось так:
>>> factory(App\Category::class, 5)->create()->each(function ($category) {
$category->products()->saveMany(factory(App\Product::class, 10)->make());
});
Тепер же достатньо такого запису:
>>> factory(App\Category::class, 5)->create()->each->createFakeProducts(10);
На цьому все, сподіваюся, даний матеріал був корисним. В офіційній документації на цю тему сказано небагато, тому в даному випадку (втім, як і завжди) кращий спосіб вивчення - експерименти і застосування на практиці.
Успіхів!