Laravel - Множество аксессоров (мутаторов) для рендеринга представлений с помощью Blade - PullRequest
6 голосов
/ 07 мая 2020

У меня есть проект Laravel 7 с необходимостью большого преобразования данных из модели в представления перед отображением.

Я думал об использовании Laravel Аксессоров и использовать их прямо в моем клинке. php файлов.
Но когда я закончил работу с простой таблицей html, я посмотрел на свой код и думаю, что там слишком много аксессуаров, и даже некоторые из них имеют трудные имя для чтения.

Вид лезвия

@foreach($races as $race)
<tr>
    <td>{{ $race->display_dates }}</td>
    <td>{{ $race->display_name }}</td>
    <td>{{ $race->type->name }}</td>
    <td>{{ $race->display_price }}</td>
    <td>{{ $race->display_places }}</td>
    <td>{{ $race->online_registration_is_open ? 'Yes' : 'No' }}</td>
</tr>
@endforeach

Контроллер

public function show(Group $group)
{
    $races = $group->races;
    $races->loadMissing('type'); // Eager loading
    return view('races', compact('races'));
}

Модель

// Accessors
public function getOnlineRegistrationIsOpenAttribute()
{
    if (!$this->online_registration_ends_at && !$this->online_registration_starts_at) return false;
    if ($this->online_registration_ends_at < now()) return false;
    if ($this->online_registration_starts_at > now()) return false;
    return true;
}

public function getNumberOfParticipantsAttribute()
{
    return $this->in_team === true
        ? $this->teams()->count()
        : $this->participants()->count();
}

// Accessors mainly used for displaying purpose
public function getDisplayPlacesAttribute()
{
    if ($this->online_registration_ends_at < now()) {
        return "Closed registration";
    }
    if ($this->online_registration_starts_at > now()) {
        return "Opening date: " . $this->online_registration_starts_at;
    }
    return "$this->number_of_participants / $this->max_participants";
}

public function getDisplayPriceAttribute()
{
    $text = $this->online_registration_price / 100;
    $text .= " €";
    return $text;
}

public function getDisplayDatesAttribute()
{
    $text = $this->starts_at->toDateString();
    if ($this->ends_at) { $text .= " - " . $this->ends_at->toDateString(); }
    return $text;
}

public function getDisplayNameAttribute()
{
    $text = $this->name;
    if ($this->length) { $text .= " $this->length m"; }
    if ($this->elevation) { $text .= " ($this->elevation m)"; }
    return $text;
}

Этот код работает, но я думаю, что у него много минусов : читаемость, возможны ошибки, например, если связанная таблица БД имеет столбец name, пока я создаю здесь аксессор getDisplayNameAttribute.
И это только начало, думаю, мне понадобится еще 30-40 аксессоров для других представлений ... Кроме того, мне нужно будет использовать некоторые из них много раз, например, getDisplayNameAttribute можно использовать на обычной странице и странице администратора (и, возможно, больше).

Я также посмотрел на JsonResource и View Composer но JsonResource, похоже, для APIs, а ViewComposer, похоже, специально для Views.

Я также подумал о том, чтобы добавить к аксессуарам что-то вроде acc_, чтобы уменьшить количество ошибок с существующим столбцом db:

public function getAccDisplayNameAttribute() { ... };

Но я действительно не думаю, что это решение, я даже не уверен, что я делаю правильно или неправильно. Я также безуспешно искал лучшие практики по inte rnet.

Ответы [ 3 ]

4 голосов
/ 11 мая 2020

У меня есть 2 решения для этого случая, которые я тестировал на моей локальной машине. Хотя я не уверен, соответствуют ли они передовым методам или принципам проектирования, это нужно увидеть, но я уверен, что они организуют код управляемым способом.

Во-первых, вы можете создать аксессуар, который - это массив и объедините все ваши logi c внутри него, которые будут вычислять и возвращать массив. Это может сработать, возможно, для 4-5 атрибутов, и вам придется внести изменения в свои представления, чтобы получить доступ к массиву вместо свойств. Пример кода 1 приведен ниже

Второй способ - создать отдельный класс, который будет содержать все различные вычислительные логики c как методы. Допустим, вы создаете класс ModelAccessor, затем вы можете создавать аксессоры в своем классе Model точно так же, как вы делаете сейчас, и возвращать ModelAccessor-> someMethod изнутри каждого из них. Это добавит аккуратности вашему классу Model, и вы сможете легко управлять логикой вычислений c из методов класса. Пример кода 2 ниже может сделать его более понятным.

  1. Пример 1 для массивов в качестве средств доступа. Позволяет вызвать возвращенный атрибут $stats

public function getStatsAttribute(){

   $stats = [];


   if (!$this->online_registration_ends_at && !$this->online_registration_starts_at) $o=false;
    if ($this->online_registration_ends_at < now()) $o = false;
    if ($this->online_registration_starts_at > now()) $o = false;
    $o= true;

    $stats['online_registration_is_open'] = $o;

    $stats['number_of_participants'] = $this->in_team === true
        ? $this->teams()->count()
        : $this->participants()->count();

    return $stats;
}

Вам придется изменить просмотр файлов для использования массива stats

<td>{{ $race->stats['number_of_participants'] }} </td>
<td>{{ $race->stats["online_registration_is_open"] ? 'Yes' : 'No' }}</td>

Это может стать беспорядочным для большого количества атрибутов. Чтобы избежать этого, вы можете иметь несколько массивов, группирующих похожие вещи ($ stats, $ payment_details, $ race_registration, et c), или вы можете использовать отдельный класс для управления ими всеми, как в следующем примере

Пример 2, отдельный класс с методами для установки различных атрибутов
class ModelAccessors
{
protected $race;

function __construct(\App\Race $race){

     $this->race = $race;
}

public function displayPrice()
{
    $text = $race->online_registration_price / 100;
    $text .= " €";
    return $text;
}

Затем внутри модели

public function getDisplayPriceAttribute()
{
    $m = new ModelAccessor($this);

    return $m->displayPrice();
}

Используя это, вам не нужно обновлять файлы лезвий

3. Если у вас 30-40 аксессуаров, я думаю, что поддерживать отдельный класс со всеми методами будет намного проще. В дополнение к этому вы можете создать массив атрибутов из самого класса и называть его так:

class ModelAccessors
{
protected $race;
protected $attributes;

function __construct(\App\Race $race){

     $this->race = $race;
     $this->attributes = [];

}

public function displayPrice()
{
    $text = $race->online_registration_price / 100;
    $text .= " €";
    $this->attributes['display_price'] = $text;
}

public function allStats(){

    $this->displayPrice();
    $this->someOtherMethod();
    $this->yetAnotherMethod();
    // ..
    // you can further abstract calling of all the methods either from 
    // other method or any other way

    return $this->attributes;
}
// From the model class
public function getStatsAccessor()
{
    $m = new ModelAccessor($this);

    // Compute the different values, add them to an array and return it as
    // $stats[
    //         "display_price"= "234 €",
    //         "number_of_participants" = 300;
    //  ]
    return $m->allStats() 
}
3 голосов
/ 16 мая 2020

Вы можете создать класс, специально обрабатывающий ваши преобразования. Затем вы можете загрузить экземпляр этого класса в конструктор модели. Вы также можете заставить свои классы доступа использовать PHP magi c геттеры, если вы используете sh.

Accessors Class

class RaceAccessors {
    // The related model instance
    private $model;

    // Already generated results to avoid some recalculations
    private $attributes = [];

    function __construct($model)
    {
        $this->model = $model;
    }

    // Magic getters
    public function __get($key)
    {
        // If already called once, just return the result
        if (array_key_exists($key, $this->attributes)) {
            return $this->attributes[$key];
        }

        // Otherwise, if a method with this attribute name exists
        // Execute it and store the result in the attributes array
        if (method_exists(self::class, $key)) {
            $this->attributes[$key] = $this->$key($value);
        }

        // Then return the attribute value
        return $this->attributes[$key];
    }

    // An example of accessor 
    public function price()
    {
        $text = $this->model->online_registration_price  / 100;
        $text .= " €";
        return $text;
    }
}

Model Class

class Race {
    // The Accessors class instance
    public $accessors;

    function __construct()
    {
        $this->accessors = new \App\Accessors\RaceAccessors($this);
    }
}

View

@foreach($races as $race)
<tr>
    <td>{{ $race->accessors->price }}</td>
    [...]
</tr>
@endforeach

Я не устанавливал здесь тип для переменной $model, потому что вы можете использовать тот же accessors class для других моделей, которым необходимо преобразовать некоторые поля таким же образом, как этот класс аксессуаров делает.

Например, аксессуар цены может работать для гонки (в вашем случае), но он также может работать и для других моделей, таким образом вы можете представить создание групп аксессуаров, используемых многими моделями, без изменения слишком большого количества кода на справиться с этим.

0 голосов
/ 11 мая 2020

Аксессоры и мутаторы - это обычные функции получения и установки в Laravel. Это просто более «элегантный» способ выразить это.

Я мог бы сказать, что функции получения и установки должны дать вам лучший контроль над вашими переменными в вашем классе, но это не сильно поможет понять это. бит.

Так для чего это? Зачем его использовать?

Мы уже знакомы с принципом DRY (не повторяйтесь), который гласит, что дублирование - это logi c, от которого следует избавиться. Или на кухонном языке; не пишите один и тот же код дважды, потому что это отнимает у вас время без всякой причины.

Так как это помогает?

Например, у вас есть класс проверки орфографии, который использует другое приложение на сервере, например hunspell. Для небольших строк вы можете использовать его напрямую, и когда приходит запрос, вы обрабатываете его, вызывая команду с shell_exe c, затем вы получаете результат и возвращаете его - все довольны.

Однако, если текст слишком длинный, то php превысит 30-секундный лимит (по умолчанию), и вы не получите никаких результатов. В этом случае вы можете создать сценарий, который запускается процессом демона, поэтому ограничение в 30 секунд не будет проблемой. Когда поступает запрос, вы передаете текст процессу-демону, например, используя сокет unix, затем фоновый процесс выполняет проверку орфографии за вас и отправляет результат.

В обоих случаях у вас есть для установки определенных переменных для класса проверки орфографии, которые могут нуждаться в проверке или форматировании. И вы не хотите развивать его дважды, потому что это требует времени, наводит беспорядок и увеличивает количество возможных мест, где вы можете что-то испортить.

Бывают случаи, когда их намного больше, чем просто 2 места где вам нужно использовать препараты для установки или получения данных, и даже только в двух местах это может очень помочь избежать дублирования кода. Это также может помочь предотвратить увеличение сложности вашего кода.

Это еще не все!

Иногда вы пишете классы, и бог знает, кто будет их использовать. Может это для пакета publi c. Затем, когда разработчик получает пакет, он не хочет заботиться о том, как будет проверяться электронное письмо. Он не хочет знать, почему \n должно быть удалено из конца строки для определенных c переменных, когда до того, как он получит переменную из файла. Он не хочет заботиться о реализации части проверки целочисленных значений. Он просто хочет использовать пакет, чтобы выполнить работу как можно скорее.

То, что вы делаете с сеттерами и геттерами, - это не что иное, как защита переменных вашего класса путем управления ими с помощью этих методов.

Иногда вы хотите скрыть свои переменные в своем классе, чтобы другие классы не зависели от них. Это дает вам гибкость при изменении реализации и типа переменной.

Стоит ли всегда их использовать?

Нет. Вызов функций в php выполняется медленно. Просто проверьте, насколько медленнее Laravel по сравнению с собственным кодом PHP. Иногда написание геттеров и сеттеров совершенно бессмысленно и просто пустая трата времени.

Когда мне тогда его использовать?

Используйте его, когда вам нужно ограничить доступ к вашим переменным. Создание геттеров и сеттеров для каждого поля - это излишне. Это действительно зависит от ситуации, поэтому используйте его только там, где это необходимо.

Подробнее об этом c

Getter and Setter?

Зачем нужны геттеры и сеттеры / аксессоры?

https://dev.to/scottshipp/avoid-getters-and-setters-whenever-possible-c8m

...