2.2. Свойства

Свойства хранят и управляют данными внутри ваших компонентов Livewire. Они определяются как публичные свойства в классах компонентов и могут быть доступны и изменяемы как на стороне сервера, так и на стороне клиента.

1. Инициализация свойств

Вы можете задать начальные значения для свойств внутри метода mount() вашего компонента.

Рассмотрим следующий пример:

<?php
 
namespace App\Livewire;
 
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
 
class TodoList extends Component
{
public $todos = [];
 
public $todo = '';
 
public function mount()
{
$this->todos = Auth::user()->todos;
}
 
// ...
}

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

2. Массовое присваивание

Иногда инициализация множества свойств в методе mount() может казаться избыточной. Чтобы упростить этот процесс, Livewire предоставляет удобный способ одновременного присваивания нескольких свойств с помощью метода fill(). Передав ассоциативный массив с именами свойств и их соответствующими значениями, вы можете установить несколько свойств одновременно и сократить количество повторяющихся строк кода в mount().

Например:

<?php
 
namespace App\Livewire;
 
use Livewire\Component;
use App\Models\Post;
 
class UpdatePost extends Component
{
public $post;
 
public $title;
 
public $description;
 
public function mount(Post $post)
{
$this->post = $post;
 
$this->fill(
$post->only('title', 'description'),
);
}
 
// ...
}

Поскольку $post->only(...) возвращает ассоциативный массив атрибутов модели и их значений на основе переданных имён, свойства $title и $description будут изначально установлены в значения title и description модели $post из базы данных без необходимости присваивать каждое из них вручную.

3. Привязка данных

Livewire поддерживает двустороннюю привязку данных с помощью HTML-атрибута wire:model. Это позволяет легко синхронизировать данные между свойствами компонента и HTML-элементами ввода, поддерживая интерфейс пользователя и состояние компонента в согласованном виде.

Давайте используем директиву wire:model, чтобы привязать свойство $todo в компоненте TodoList к простому элементу ввода:

<?php
 
namespace App\Livewire;
 
use Livewire\Component;
 
class TodoList extends Component
{
public $todos = [];
 
public $todo = '';
 
public function add()
{
$this->todos[] = $this->todo;
 
$this->todo = '';
}
 
// ...
}
<div>
<input type="text" wire:model="todo" placeholder="Todo...">
 
<button wire:click="add">Add Todo</button>
 
<ul>
@foreach ($todos as $todo)
<li>{{ $todo }}</li>
@endforeach
</ul>
</div>

В приведённом выше примере значение текстового поля будет синхронизироваться со свойством $todo на сервере при нажатии на кнопку "Add Todo".

Это лишь поверхностное знакомство с wire:model. Для более подробной информации о привязке данных ознакомьтесь с нашей документацией по формам.

4. Сброс свойств

Иногда может потребоваться сбросить свойства к их исходному состоянию после того, как пользователь выполнит какое-либо действие. В таких случаях Livewire предоставляет метод reset(), который принимает одно или несколько имён свойств и сбрасывает их значения к начальному состоянию.

В приведённом ниже примере мы можем избежать дублирования кода, используя $this->reset() для сброса поля todo после нажатия на кнопку "Add Todo":

<?php
 
namespace App\Livewire;
 
use Livewire\Component;
 
class ManageTodos extends Component
{
public $todos = [];
 
public $todo = '';
 
public function addTodo()
{
$this->todos[] = $this->todo;
 
$this->reset('todo');
}
 
// ...
}

В приведённом выше примере, после того как пользователь нажимает "Add Todo", поле ввода с только что добавленной задачей очищается, позволяя пользователю ввести новую задачу.

reset() не работает со значениями, установленными в mount()

Метод reset() сбрасывает свойство к состоянию, в котором оно было до вызова метода mount(). Если вы инициализировали свойство в mount() другим значением, вам придётся сбросить его вручную.

5. Извлечение свойств

В качестве альтернативы вы можете использовать метод pull(), чтобы одновременно сбросить и получить значение свойства за одну операцию.

Вот тот же пример, что и выше, но упрощённый с использованием pull():

<?php
 
namespace App\Livewire;
 
use Livewire\Component;
 
class ManageTodos extends Component
{
public $todos = [];
 
public $todo = '';
 
public function addTodo()
{
$this->todos[] = $this->pull('todo');
}
 
// ...
}

Приведённый выше пример извлекает одно значение, но pull() также можно использовать для сброса и получения (в виде пары ключ-значение) всех или некоторых свойств:

// То же самое, что $this->all() и $this->reset();
$this->pull();
 
// То же самое, что $this->only(...) и $this->reset(...);
$this->pull(['title', 'content']);

6. Поддерживаемые типы свойств

Livewire поддерживает ограниченный набор типов свойств из-за своего уникального подхода к управлению данными компонентов между запросами к серверу.

Каждое свойство в компоненте Livewire сериализуется или "обезвоживается" в JSON между запросами, а затем "гидратируется" из JSON обратно в PHP для следующего запроса.

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

6.1. Примитивные типы

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

Livewire поддерживает следующие примитивные типы свойств: Array, String, Integer, Float, Boolean и Null.

class TodoList extends Component
{
public $todos = []; // Array
 
public $todo = ''; // String
 
public $maxTodos = 10; // Integer
 
public $showTodos = false; // Boolean
 
public $todoFilter; // Null
}

6.2. Обычные типы PHP

Помимо примитивных типов, Livewire поддерживает обычные объектные типы PHP, используемые в приложениях Laravel. Однако важно отметить, что эти типы будут обезвожены в JSON и гидратированы обратно в PHP при каждом запросе. Это означает, что свойство может не сохранять значения, формируемые во время выполнения, например замыкания. Также информация об объекте, такая как имя класса, может быть раскрыта JavaScript.

Поддерживаемые типы PHP:

Тип Полное имя класса
BackedEnum BackedEnum
Collection Illuminate\Support\Collection
Eloquent Collection Illuminate\Database\Eloquent\Collection
Model Illuminate\Database\Eloquent\Model
DateTime DateTime
Carbon Carbon\Carbon
Stringable Illuminate\Support\Stringable
Eloquent-коллекции и Модели

При сохранении Eloquent-коллекций и моделей в свойствах Livewire, дополнительные ограничения запроса, такие как select(...), не будут повторно применяться при последующих запросах.

См. раздел Ограничения Eloquent не сохраняются между запросами для получения дополнительных сведений.

Вот простой пример установки свойств с использованием различных типов:

public function mount()
{
$this->todos = collect([]); // Collection
 
$this->todos = Todos::all(); // Eloquent Collection
 
$this->todo = Todos::first(); // Model
 
$this->date = new DateTime('now'); // DateTime
 
$this->date = new Carbon('now'); // Carbon
 
$this->todo = str(''); // Stringable
}

6.3. Поддержка пользовательских типов

Livewire позволяет вашему приложению поддерживать пользовательские типы с помощью двух мощных механизмов:

  • Wireable-объекты
  • Синтезаторы

Wireable-объекты просты и удобны для большинства приложений, поэтому мы рассмотрим их ниже. Если вы опытный разработчик или автор пакета и хотите большей гибкости, синтезаторы — это ваш выбор.

6.3.1. Wireable-объекты

Wireable-объектом является любой класс в вашем приложении, реализующий интерфейс Wireable.

Например, представим, что в вашем приложении есть объект Customer, содержащий основную информацию о клиенте:

class Customer
{
protected $name;
protected $age;
 
public function __construct($name, $age)
{
$this->name = $name;
$this->age = $age;
}
}

Попытка установить экземпляр этого класса в качестве свойства компонента Livewire приведёт к ошибке, сообщающей, что тип свойства Customer не поддерживается:

class ShowCustomer extends Component
{
public Customer $customer;
 
public function mount()
{
$this->customer = new Customer('Caleb', 29);
}
}

Однако вы можете решить эту проблему, реализовав интерфейс Wireable и добавив в ваш класс методы toLivewire() и fromLivewire(). Эти методы сообщают Livewire, как преобразовывать свойства этого типа в JSON и обратно:

use Livewire\Wireable;
 
class Customer implements Wireable
{
protected $name;
protected $age;
 
public function __construct($name, $age)
{
$this->name = $name;
$this->age = $age;
}
 
public function toLivewire()
{
return [
'name' => $this->name,
'age' => $this->age,
];
}
 
public static function fromLivewire($value)
{
$name = $value['name'];
$age = $value['age'];
 
return new static($name, $age);
}
}

Теперь вы можете свободно устанавливать объекты Customer в свойствах компонентов Livewire, и Livewire будет знать, как преобразовать эти объекты в JSON и обратно в PHP.

Как упоминалось ранее, если вы хотите более глобально и гибко поддерживать различные типы, Livewire предлагает синтезаторы — продвинутый внутренний механизм для работы с различными типами свойств. Узнайте больше о синтезаторах.

7. Доступ к свойствам из JavaScript

Поскольку свойства Livewire также доступны в браузере через JavaScript, вы можете получать к ним доступ и управлять их JavaScript-представлениями с помощью AlpineJS.

Alpine — это лёгкая JavaScript-библиотека, включённая в состав Livewire. Она позволяет добавлять простые интерактивные элементы в компоненты Livewire без необходимости полного запроса к серверу.

Внутри интерфейс Livewire построен поверх Alpine. На самом деле каждый компонент Livewire — это Alpine-компонент под капотом. Это означает, что вы можете свободно использовать Alpine внутри ваших компонентов Livewire.

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

7.1. Доступ к свойствам

Livewire предоставляет волшебный объект $wire для Alpine. Вы можете обращаться к объекту $wire из любого выражения Alpine внутри вашего компонента Livewire.

Объект $wire можно воспринимать как JavaScript-версию вашего компонента Livewire. Он содержит те же свойства и методы, что и PHP-версия компонента, а также несколько специальных методов для выполнения определённых функций в шаблоне.

Например, мы можем использовать $wire для отображения счётчика символов в реальном времени для поля ввода todo:

<div>
<input type="text" wire:model="todo">
 
Длина задачи в символах: <h2 x-text="$wire.todo.length"></h2>
</div>

По мере ввода текста пользователем, длина текущей задачи будет отображаться и обновляться в реальном времени на странице — всё это без отправки сетевого запроса на сервер.

Если вам удобнее, вы можете использовать более явный метод .get() для достижения того же результата:

<div>
<input type="text" wire:model="todo">
 
Длина задачи в символах: <h2 x-text="$wire.get('todo').length"></h2>
</div>

7.2. Изменение свойств

Аналогично, вы можете изменять свойства вашего компонента Livewire в JavaScript с помощью $wire.

Например, давайте добавим кнопку "Очистить" в компонент TodoList, чтобы пользователь мог сбросить поле ввода, используя только JavaScript:

<div>
<input type="text" wire:model="todo">
 
<button x-on:click="$wire.todo = ''">Очистить</button>
</div>

После того как пользователь нажмёт "Очистить", поле ввода будет сброшено до пустой строки — без отправки сетевого запроса на сервер.

При следующем запросе серверное значение $todo будет обновлено и синхронизировано.

Если вам удобнее, вы также можете использовать более явный метод .set() для установки свойств на стороне клиента. Однако имейте в виду, что при использовании .set() по умолчанию сразу отправляется сетевой запрос и состояние синхронизируется с сервером. Если это именно то, что нужно — это отличный API:

<button x-on:click="$wire.set('todo', '')">Очистить</button>

Чтобы обновить свойство без отправки сетевого запроса на сервер, вы можете передать третьим параметром булево значение. Это отложит сетевой запрос, и при следующем обращении состояние будет синхронизировано на стороне сервера:

<button x-on:click="$wire.set('todo', '', false)">Очистить</button>

8. Вопросы безопасности

Хотя свойства Livewire являются мощной функциональностью, существует несколько аспектов безопасности, о которых следует знать перед их использованием.

Короче говоря, всегда рассматривайте публичные свойства как пользовательский ввод — так же, как если бы это были данные из запроса к обычной конечной точке. В связи с этим крайне важно выполнять валидацию и авторизацию свойств перед их сохранением в базу данных — точно так же, как вы делаете это при работе с вводом запроса в контроллере.

8.1. Не доверяйте значениям свойств

Чтобы показать, как пренебрежение авторизацией и валидацией свойств может привести к уязвимостям в вашем приложении, ниже приведён компонент UpdatePost, подверженный атаке:

<?php
 
namespace App\Livewire;
 
use Livewire\Component;
use App\Models\Post;
 
class UpdatePost extends Component
{
public $id;
public $title;
public $content;
 
public function mount(Post $post)
{
$this->id = $post->id;
$this->title = $post->title;
$this->content = $post->content;
}
 
public function update()
{
$post = Post::findOrFail($this->id);
 
$post->update([
'title' => $this->title,
'content' => $this->content,
]);
 
session()->flash('message', 'Post updated successfully!');
}
 
public function render()
{
return view('livewire.update-post');
}
}
<form wire:submit="update">
<input type="text" wire:model="title">
<input type="text" wire:model="content">
 
<button type="submit">Обновить</button>
</form>

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

Поскольку мы сохраняем id поста как публичное свойство компонента, его можно изменить на стороне клиента так же, как свойства title и content.

Не имеет значения, что мы не добавили поле ввода с wire:model="id". Злоумышленник легко может изменить представление следующим образом, используя инструменты разработчика в браузере:

<form wire:submit="update">
<input type="text" wire:model="id">
<input type="text" wire:model="title">
<input type="text" wire:model="content">
 
<button type="submit">Обновить</button>
</form>

Теперь злоумышленник может изменить значение поля id на идентификатор другого поста. Когда форма будет отправлена и вызовется метод update(), Post::findOrFail() вернёт и обновит пост, владельцем которого пользователь не является.

Чтобы предотвратить такого рода атаку, можно использовать одну или обе из следующих стратегий:

  • Авторизовать ввод
  • Заблокировать обновление свойства

8.1.1. Авторизация ввода

Поскольку свойство $id может быть изменено на стороне клиента с помощью wire:model, как и в контроллере, мы можем использовать систему авторизации Laravel, чтобы убедиться, что текущий пользователь имеет право обновлять пост:

public function update()
{
$post = Post::findOrFail($this->id);
 
$this->authorize('update', $post);
 
$post->update(...);
}

Если злоумышленник изменит значение свойства $id, добавленная авторизация перехватит это и выбросит ошибку.

8.1.2. Блокировка свойств

Livewire также позволяет «заблокировать» свойства, чтобы предотвратить их изменение на стороне клиента. Вы можете «заблокировать» свойство от клиентских изменений с помощью атрибута #[Locked]:

use Livewire\Attributes\Locked;
use Livewire\Component;
 
class UpdatePost extends Component
{
#[Locked]
public $id;
 
// ...
}

Теперь, если пользователь попытается изменить $id на клиентской стороне, будет выброшена ошибка.

Используя #[Locked], вы можете быть уверены, что это свойство не было изменено нигде вне класса вашего компонента.

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

8.1.3. Eloquent-модели и блокировка

Когда Eloquent-модель присваивается свойству компонента Livewire, Livewire автоматически блокирует это свойство и гарантирует, что его ID не будет изменён, защищая вас от подобных атак:

<?php
 
namespace App\Livewire;
 
use Livewire\Component;
use App\Models\Post;
 
class UpdatePost extends Component
{
public Post $post;
public $title;
public $content;
 
public function mount(Post $post)
{
$this->post = $post;
$this->title = $post->title;
$this->content = $post->content;
}
 
public function update()
{
$this->post->update([
'title' => $this->title,
'content' => $this->content,
]);
 
session()->flash('message', 'Пост успешно обновлён!');
}
 
public function render()
{
return view('livewire.update-post');
}
}

8.2. Свойства раскрывают системную информацию в браузере

Ещё один важный момент, который следует помнить: свойства Livewire сериализуются или «обезвоживаются» перед отправкой в браузер. Это означает, что их значения преобразуются в формат, который можно передать по сети и который понимается JavaScript. Такой формат может раскрывать информацию о вашем приложении в браузере, включая имена и классы ваших свойств.