22 677 Монеток
Атрибуты в PHP — это отличный способ добавлять метаданные к вашим классам, методам и свойствам. Laravel предоставляет множество готовых атрибутов, которые можно использовать для улучшения структуры и читаемости вашего кода.
(ObservedBy)
Атрибут ObservedBy
позволяет указать наблюдателя для модели. Это помогает держать код модели чистым, перенося логику наблюдателя в отдельный класс.
use App\Observers\UserObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
#[ObservedBy([UserObserver::class])]
class User extends Authenticatable
{
// ...
}
(ScopedBy)
Атрибут ScopedBy
позволяет назначить глобальные условия для модели. Это полезно, когда нужно применять общие ограничения к запросам для конкретной модели.
namespace App\Models;
use App\Models\Scopes\ActiveScope;
use Illuminate\Database\Eloquent\Attributes\ScopedBy;
#[ScopedBy([ActiveScope::class])]
class User extends Model
{
// ...
}
Laravel упрощает процесс внедрения зависимостей в зависимости от контекста, например, для внедрения драйверов или настроек. Вместо того чтобы вручную настраивать такие зависимости в сервис-провайдерах, Laravel предлагает ряд контекстных атрибутов, которые облегчают эту задачу.
Пример:
namespace App\Http\Controllers;
use Illuminate\Container\Attributes\Auth;
use Illuminate\Container\Attributes\Cache;
use Illuminate\Container\Attributes\Config;
use Illuminate\Container\Attributes\DB;
use Illuminate\Container\Attributes\Log;
use Illuminate\Container\Attributes\Tag;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Contracts\Cache\Repository;
use Illuminate\Contracts\Database\Connection;
use Psr\Log\LoggerInterface;
class PhotoController extends Controller
{
public function __construct(
#[Auth('web')] protected Guard $auth,
#[Cache('redis')] protected Repository $cache,
#[Config('app.timezone')] protected string $timezone,
#[DB('mysql')] protected Connection $connection,
#[Log('daily')] protected LoggerInterface $log,
#[Tag('reports')] protected iterable $reports,
)
{
// ...
}
}
Laravel также поддерживает автоматическое внедрение текущего пользователя в маршруты:
use App\Models\User;
use Illuminate\Container\Attributes\CurrentUser;
Route::get('/user', function (#[CurrentUser] User $user) {
return $user;
})->middleware('auth');
(DeleteWhenMissingModels)
При использовании моделей в заданиях вы можете добавить атрибут DeleteWhenMissingModels
, чтобы задание автоматически удалялось, если указанная модель отсутствует в базе данных.
namespace Acme;
use Illuminate\Queue\Attributes\DeleteWhenMissingModels;
#[DeleteWhenMissingModels]
class ProcessPodcastJob
{
public function __construct(
public Podcast $podcast,
) {}
}
(WithoutRelations)
Атрибут WithoutRelations
позволяет загрузить модель без её связей. Это удобно, если вам не нужны связанные данные в фоновых задачах.
class ProcessPodcastJob
{
public function __construct(
#[WithoutRelations]
public Podcast $podcast,
) {}
}
Новые атрибуты постепенно появляются, что бы упростить работу с кодом.
А какие атрибуты хотели бы видеть вы? 💡
С ремеслом дружу
На конференции ApiPlatformCon во Франции Кевин Дюнглас (создатель Mercure и FrankenPHP) объявил о поддержке API Platform для Laravel! 🚀
API Platform — это мощный инструмент для создания API, который автоматизирует множество задач, таких как валидация, сортировка и фильтрация данных. До недавнего времени его активно использовали в проектах на Symfony, но теперь Laravel-разработчики тоже могут легко интегрировать его в свои проекты.
Основные возможности:
Более детальную информацию можно узнать на официальном сайте: api-platform.com. 🌐
Если вы видите это, значит, я еще не придумал, что написать.
Поиск — это неотъемлемая часть многих приложений: будь то поиск ближайшей заправки, нахождение учебного пособия на YouTube или поиск старого сообщения в чате. В этом посте мы рассмотрим, как реализовать функцию поиска в приложениях на Laravel. Мы начнем с базовых запросов MySQL с использованием оператора LIKE и перейдем к более производительным решениям с помощью полнотекстовых индексов и Typesense.
Представим приложение для обслуживания клиентов, где требуется быстро находить аккаунты клиентов по имени, электронной почте или адресу. Скорость поиска критична для эффективного ответа на запросы. Создадим это приложение и назовем его StarSupport:
composer create-project laravel/laravel starsupport
Или с использованием Laravel Installer:
laravel new starsupport
Выберите “No starter kit” при установке и настройте подключение к базе данных MySQL в файле .env.
Теперь создадим модель Customer с миграцией, фабрикой и сидером:
php artisan make:model Customer --migration --factory --seed
Эта команда создаст четыре файла:
app/Models/Customer.php database/migrations/2024_06_20_135645_create_customers_table.php database/factories/CustomerFactory.php database/seeders/CustomerSeeder.php Настроим миграцию:
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->string('account_number')->unique();
$table->string('address');
$table->string('country');
$table->string('phone');
$table->timestamps();
Настроим фабрику для генерации фейковых данных:
'name' => fake()->firstName() . ' ' . fake()->lastName(),
'email' => fake()->unique()->safeEmail(),
'account_number' => fake()->unique()->randomNumber(8, true),
'address' => fake()->address(),
'country' => fake()->country(),
'phone' => fake()->phoneNumber(),
Используем фабрику в CustomerSeeder для создания двух миллионов записей:
public function run(): void
{
$total = 2_000_000;
$chunkSize = 100_000;
for ($i = 0; $i < $total; $i += $chunkSize) {
Customer::factory()->count($chunkSize)->create();
}
}
Не забудьте вызвать этот сидер в DatabaseSeeder:
public function run(): void
{
User::factory()->create([
'name' => 'Test User',
'email' => '[email protected]',
]);
$this->call(CustomerSeeder::class);
}
Теперь установим заполняемость всех полей модели Customer:
protected $guarded = [];
Заполним базу данных:
php artisan migrate:fresh --seed
Цель — создать функцию поиска клиентов по имени, электронной почте или адресу с помощью ключевого слова. Для этого создадим метод scope в модели Customer:
public function scopeSearch(Builder $query, string $keyword): Builder
{
return $query->where('name', 'LIKE', "%{$keyword}%")
->orWhere('email', 'LIKE', "%{$keyword}%")
->orWhere('address', 'LIKE', "%{$keyword}%");
}
Проведем тестирование производительности этого запроса с помощью встроенного помощника Benchmark:
use App\Models\Customer;
use Illuminate\Support\Benchmark;
Benchmark::dd(fn () => Customer::search('john')->get());
Результат может быть около 3 секунд. Это недостаточно быстро для поиска в базе данных из двух миллионов записей.
С версии 5.6 MySQL поддерживает полнотекстовые индексы, которые улучшают производительность поиска по тексту. Создадим новый миграционный файл для добавления полнотекстового индекса:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('customers', function (Blueprint $table) {
$table->fullText(['name', 'email', 'address']);
});
}
public function down(): void
{
Schema::table('customers', function (Blueprint $table) {
$table->dropFullText(['name', 'email', 'address']);
});
}
};
Обновим метод scope для использования полнотекстового поиска:
public function scopeSearch(Builder $query, string $keyword): Builder
{
return $query->whereFullText(['name', 'email', 'address'], $keyword);
}
Проведем тестирование производительности:
Benchmark::dd(fn () => Customer::search(Str::random(4))->get(), iterations: 10);
Результат может быть около 3 миллисекунд, что значительно быстрее.
Установим Livewire:
composer require livewire/livewire
Создадим компонент поиска:
php artisan make:livewire customer-search
Редактируем компонент:
<?php
namespace App\Livewire;
use Livewire\Component;
use App\Models\Customer;
use Illuminate\Support\Collection;
class CustomerSearch extends Component
{
public string $keyword = '';
public Collection $customers;
public function search()
{
$this->customers = strlen($this->keyword) > 2
? Customer::search($this->keyword)->take(20)->get()
: collect([]);
}
public function render()
{
return view('livewire.customer-search');
}
}
Обновим шаблон компонента:
<div class="customer-search">
<input
wire:model="keyword"
wire:keyup.debounce="search"
autofocus
placeholder="Search" />
@if ($keyword)
<ul>
@forelse ($customers as $customer)
<li>
<div>{{ $customer['name'] }}</div>
<div>{{ $customer['email'] }}</div>
<div>{{ $customer['address'] }}</div>
</li>
@empty
<li>
No matches found
</li>
@endforelse
</ul>
@endif
</div>
Добавим выделение ключевых слов в результатах поиска:
use Livewire\Attributes\Computed;
#[Computed]
public function highlightedCustomers()
{
$fields = ['name', 'email', 'address'];
$highlight = fn ($value) => preg_replace("/({$this->keyword})/i",'<mark>$1</mark>',$value);
return $this->customers
->map(fn ($customer) => array_map($highlight, $customer->only($fields)));
}
И обновим шаблон:
@forelse ($this->highlightedCustomers as $customer)
<li>
<div>{!! $customer['name'] !!}</div>
<div>{!! $customer['email'] !!}</div>
<div>{!! $customer['address'] !!}</div>
</li>
@empty
<li>
No matches found
</li>
@endforelse
Отсутствие терпимости к ошибкам Поиск по ключевым словам не позволяет находить записи с ошибками в запросе. Пример: “jhon” вместо “john”.
Отсутствие поддержки суффиксного и инфиксного поиска Поиск по полнотекстовому индексу не поддерживает совпадения по частям слова. Пример: “tom*” не найдет записи с “atom”.
Проблемы с взвешиванием результатов Поиск с учетом значимости результатов не поддерживается.
Мы рассмотрели, как использовать MySQL полнотекстовые индексы и Typesense для повышения производительности поиска в Laravel-приложениях. Мы также создали компонент Livewire для визуализации поиска и улучшения пользовательского опыта. В будущем вы можете рассмотреть другие варианты поисковых систем для еще более сложных требований.
Источник: https://tighten.com/insights/blazing-fast-full-text-search-in-laravel-from-mysql-to-typesense/
{message}