# Konfiguracja modeli

# Filtrowanie

Uwaga

Model musi implementować trait Modules\Core\Traits\Filterable

# Rodzaje filtrów

Filtrowanie odbywa się poprzez wysłanie zapytania z odpowiednimi parametrami/atrybutami.

Dostępne rodzaje filtrów:

  • domyślny - przeszukiwany atrybut musi zawierać przeszukiwaną frazę (np. foo=bar)
  • wartość minimalna - należy wysłać atrybut jako tablicę (np. foo[min]=10)
  • wartość maksymalna (np. foo[max]=10)
  • dokładna wartość atrybutu (np. foo[strict]=bar)
  • wartość różna od przesłanej (np. foo[not]=bar)
  • przypadku przesłania listy wartości (tablicy) przeszukiwany atrybut musi zawierać przynajmniej jeden element (np. foo[]=bar&foo[]=baz)

# Filtrowanie po atrybutach modelu

Core domyślnie pozwala na filtrowanie modelu po jego wszystkich kolumnach w bazie danych.

Uwaga

Lista kolumn tabel jest wczytywana z cache, aby ograniczyć ilość zapytań SQL.

Jeśli pola w tabeli zmienią się, należy skasować cache, korzystając z komendy Laravela php artisan cache:clear

Możemy zawęzić listę filtrowanych pól dodając do modelu własność $filterable, która powinna zwierać listę atrybutów pól do filtrowania.

protected $filterable = ['first_name', 'last_name', 'email'];

TIP

Atrybut $filterable może zawierać pola wielojęzyczne

# Filtrowanie po atrybutach relacji

Dodatkowo Core pozwala na filtrowanie danych po atrybutach relacji.

W takim przypadku należy dodać relację do modelu.

public function profile()
{
    return $this->hasOne(Profile::class);
}

Następnie skorzystać z własności $relationFilterable.

protected $relationFilterable = [
  'profile_name' => [         // nazwa pola, używana do filtrowania
    'relation' => 'profile',  // nazwa relacji
    'attribute' => 'name',    // atrybut modelu relacji
  ],
];

# Filtrowanie po atrybutach niestandardowych

W modelu należy dodać metodę customFilters, która powinna zwrócić tablicę filtrów:

use Illuminate\Database\Eloquent\Builder;

protected function customFilters(): array
{
  return [
    // class based filter
    'price_over' => PriveOverFilter::class,

    // or anonymous function
    'price_under' => fn (Builder $builder, float $value) => $builder->where('price', '<', $value),
  ];
}

Filtr na bazie klasy powinien zawierać metodę apply, w której należy zdefiniować logikę filtra.

use Illuminate\Database\Eloquent\Builder;

class PriceOverFilter
{
  public function apply(Builder $query, float $value): Builder
  {
      return $query->where('price', '>', $value);
  }
}

# Wyszukiwanie

Uwaga

Model musi implementować trait Modules\Core\Traits\Searchable

Domyślnie wyszukiwanie odbywa się przekazując parametr query search.

Nazwę tego parametru można zmienić globalnie dla całej aplikacji, ustawiając default-searchable-query-param w pliku Modules\Core\Config\config.php, lub tylko dla konkretnego modelu korzystając z własności protected $searchableQueryParam.

Wyszukiwana fraza dzielona jest na części, na podstawie wyrażenia regularnego \s+, a następnie wyszukiwana jest każda z części, w którymkolwiek ze zdefiniowanych atrybutów. Jeśli jakaś część nie zostanie dopasowana - model nie zostanie zwrócony.

# Wyszukiwanie po polach modelu

protected $searchable = ['first_name', 'last_name'];

# Wyszukiwanie po polach relacji

public function profile()
{
  return $this->hasOne(Profile::class);
}
protected $customSearchable = [
  'profile' => [                // relacja
    'name', 'phone', 'email',   // atrybuty modelu relacji
  ],
];

TIP

  • Wyszukiwanie obejmuje zagnieżdżone relacje. W takim przypadku należy skorzystać z notacji z kropką

  • Lista pól relacji może zawierać pola wielojęzyczne

# Sortowanie

Sortowanie wyników wyszukiwania możliwe jest poprzez przesłanie parametrów (query string) order_by oraz order_direction.

Domyślnie wyniki wyszukiwania sortowane są malejąco po polu id.

# Sortowanie po polach modelu

Core pozwala sortować wyniki wyszukiwania modeli po wszystkich kolumnach modelu w bazie danych oraz polach tłumaczalnych.

# Sortowanie po polach relacji

Możliwe jest również sortowanie modeli po polach relacji (pola z powiązanych tabel).

Aby tego dokonać należy dodać metodę modelu public customOrders i odpowiednio ją skonfigurować.

protected function customOrders(): array
{
  return [
    // class based filter
    'role_name' => OrderByLatestRoleName::class,

    // or anonymous function
    'role_slug' => fn ($builder, $orderDirection) => $builder->orderByLatestRoleSlug($orderDirection),
  ];
}

TIP

Więcej na temat query scopes (opens new window) możesz przeczytać w dokumentacji Laravela.

# Przykład sortowania po polach translacji dla aktualnego języka

use Illuminate\Database\Eloquent\Builder;

protected function customOrders(): array
{
  return [
    'role_name' => fn ($builder, $orderDirection) => $builder
      ->leftJoin('role_translations', fn ($join) => $join
        ->on('roles.id', '=', 'role_translations.role_id')
        ->where('role_translations.locale', app()->getLocale()))
      ->orderBy('role_translations.name', $orderDirection)
  ];
}

TIP

Aby dynamicznie pobrać nazwę tabeli powiązanego modelu można skorzystać ze statycznej metody zdefiniowanej w BaseModelgetTableName

$roleTranslationTable = RoleTranslation::getTableName();

Dowiedz się więcej na temat złączeń tabel w Laravelu (opens new window)

# Walidacja

Modele rozszerzające abstrakcyjną klasę BaseModel są walidowane przed zapisaniem w bazie danych.

Reguły walidacji można zdefiniować w modelu, korzystając z metody rules.

public function rules(): array
{
  return [
    'name' => 'required',
  ];
}

Nazwy pól walidacji należy przetłumaczyć w metodzie attributes.

public function attributes(): array
{
  return [
    'name' => __('user::users.attributes.name'),
  ];
}

Natomiast własne tłumaczenia informacji zwracanych przez walidator należy zdefiniować w metodzie messages.

public function messages(): array
{
  return [
    'name.required' => __('user::users.messages.name is required'),
  ];
}

TIP

Walidacja przed zapisaniem można wyłączyć ustawiając własność validateBeforeSave na wartość false.

protected $validateBeforeSave = false;

# Generowanie slugów

Aby obsłużyć tworzenie automatyczne slugów należy dodać do modelu trait Modules\Core\Traits\Sluggable.

Domyślnie slug generowany jest na podstawie pola zdefiniowanego w pliku konfiguracyjnym Modules\Core\Config\config.php pod indeksem default-sluggable .

Jeśli chcemy, aby slug był generowany z innego pola należy dodać do modelu właność protected $sluggable.

TIP

Własność $sluggable może zawierać listę pól, z których zostanie wygenerowany slug.

protected $sluggable = ['first_name', 'last_name'];

Domyślnie implementując trait Sluggable, pole slug jest wymagane i musi być unikalne. Można nadpisać te zasady dodając do metody modelu rules pozycję slug.

W przypadku gdy zdefiniowana jest reguła unique, a slug się powtórzy, dodawany jest suffix z kolejną liczbą (np. test-1, test-2, itd.).

# Ustawianie kolejności

W przypadku potrzeby ustawiania odpowiedniej kolejności modeli, należy dodać do modelu trait Modules\Core\Traits\Sortable.

Podczas zapisywania modelu pozycja jest ustawiana automatycznie.

W przypadku zmiany wartości któregoś z pól wymienionych we własności $sortableGroup, pozycja również ulega zmianie (do modelu zostaje przypisana ostatnia pozycja w nowej grupie).

# Konfiguracja

Domyślnym polem, które będzie stanowić o kolejności jest order, które można zmienić globalnie dla całej aplikacji w pliku konfiguracyjnym Modules\Core\Config\config.php pod indeksem default-sortable-field lub dla konkretnego modelu definiując własność protected $sortableAttribute.

TIP

Model, który korzysta z trait Sortable domyślnie sortowany jest rosnąco po zdefiniowanym polu $sortableAttribute lub globalnie zdefiniowanym atrybutcie (default-sortable-field)

protected $sortableAttribute = 'position';

Jest również możliwość zdefiniowania grupy, względem której modele będą sortowane.

Ta opcja przydaje się w przypadku, gdy modele mają strukturę drzewa i potrzebujemy ustawiać ich kolejność, np. względem "rodzica" (taką strukturę mają zwykle kategorie czy pozycje menu).

protected $sortableGroup = 'parent_id';

TIP

Własność $sortableGroup może zawierać listę pól

# Zmiana pozycji modelu

Aby ustawić pozycję modelu należy skorzystać z metody setPosition, która jest zdefiniowana w Sortable trait.

$model->setPosition($position);

# Publiczne dane

Domyślnie regułą publicznego modelu jest pole active ustawione na wartość true. Jeśli model posiada tłumaczenia językowe to dodatkową regułą jest pole tłumaczenia translation_active ustawione na wartość true.

W przypadku niestandardowych reguł należy stworzyć metodę (scope) o nazwiescopePublished, która będzie zawierać niestandardowe reguły publicznych treści.

use Illuminate\Database\Eloquent\Builder;

public function scopePublished(Builder $builder): Builder
{
    return $builder
        ->whereActive(true)
        ->whereTranslation('translation_active', true, app()->getLocale())
        ->where('published_at', '>=', now());
}

Następnie nazwę klasy modelu należy dodać tablicy w pliku konfiguracyjnym publishable.php.

// config/publishable.php

return [
    \Modules\Page\Models\Page::class,
];

Ostatnim krokiem jest dodanie middleware Modules\Core\Http\Middleware\PublishedScope do routingu.

// Modules/Page/Providers/RouteServiceProvider.php

protected function mapApiRoutes()
{
    Route::prefix('api/public')
        ->middleware(['api', 'bindings', PublishedScope::class])
        ->group(__DIR__ . '/../Http/Routes/public-pages.php');
}