{{Laracast Laravel 5 Fundamentals, 14, Eloquent Relationships, 28.01.2015, 11.07.2016, https://laracasts.com/series/laravel-5-fundamentals/episodes/14}} %%(hvlraw) %% (0:00) В этом видео мы рассмотрим отношения Eloquent. Которые кстати довольно забавны. Итак, позвольте мне продемонстрировать это. Если мы рассмотрим модель Eloquent %%Article%%, то вот что у нас здесь сейчас есть. Наши поля %%$fillable%%, любые %%$dates%%, которые должны рассматриваться как экземпляры %%Carbon%%, здесь у нас есть область действия запроса, а здесь у нас есть мутатор. Так что теперь, давайте вместе подумаем. Когда у вас есть статья, она создаётся пользователем. Таким образом, пользователь может создать много статей. (0:30) И по тому же определению, мы могли бы сказать, что одна статья принадлежит пользователю, или что одна статья была написана или создана одним пользователем. Это может быть правдой и для нашего приложения, не так ли? Хорошо, следовательно, пользователь может написать много статей или, в контексте Laravel, пользователь имеет много статей и статья принадлежит пользователю. Такое здесь отношение (relationship). (1:00) И, если подумать, есть много разных вариантов, которые мы могли бы определить. Например, у статьи может быть много тегов, применённых к ней. Однако, с другой стороны, тег не принадлежит конкретной статье. У самого тега тоже может быть много статей. Таким образом, у нас есть все эти различные виды отношений. Вот ещё один. У пользователя или члена вашего сайта может быть один адрес или один телефонный номер. Но здесь всё немного по-другому. (1:30) В раннем примере, пользователь может написать много статей, однако, в данном случае у пользователя имеется ровно один номер телефона. И наоборот, этот номер телефона, опять-таки, принадлежит пользователю. Хорошо, таким образом вы поняли основы отношений один-к-одному, но как мы можем изображать их в Laravel? Сейчас я вам покажу. Давайте перейдем обратно к нашему приложению. Итак, да, правда, у нас есть класс %%Article%%, но также в Laravel по умолчанию есть ещё и класс %%User%%. (2:00) И если мы посмотрим на это, по-прежнему всё очень просто. Так что теперь, если я хочу сказать, что пользователь может написать много статей, или, другими словами, когда мы создаём статью, нам нужно каким-то образом в нашей системе связать эту статью с пользователем. Вот как это делается. Прямо здесь, как я хочу представить это отношение, эту связь? Или, другими словами, когда я пишу это, как бы я хотел, чтобы это выглядело? (2:30) ОК, у меня есть мой пользователь, и если я хочу выбрать статьи этого пользователя и только статьи этого пользователя и никого другого, то было бы здорово, если бы я мог просто сказать: %% $user->articles %% Хорошо, давайте так и попробуем сделать. Мы сделаем это методом, и теперь для Laravel и Eloquent я создам отношение вот так. Есть ли у пользователя много статей? Да, значит: %% return $this->hasMany() %% (3:00) И теперь я даю имя соответствующего класса Eloquent, в нашем случае - %%Article%%. Итак: %% return $this->hasMany('App\Article'); %% И теперь, если я тут добавлю мои docblocks, очень быстро. %%(t) Пользователь может иметь много статей. %% Круто. Таким образом, у нас готова одна сторона уравнения, но как насчет другой стороны? Что ж, давайте перейдём к %%(t)Article.php%%. И мы говорили, что статья принадлежит пользователю. Итак, как же нам это представить? (3:30) Как насчет %%$article%%, и дайте мне %%user%%(t), который создал эту статью: %% $article->user %% Довольно просто. Таким образом, у нас есть метод %%user()%%, и теперь это отношение будет таким, что статья принадлежит пользователю: %% return $this->belongsTo('App\User'); %% А потом ещё раз, настраиваем наш docblock. %%(t) Статья принадлежит пользователю. %% Теперь вот одна важная вещь для понимания, вы можете представить эту терминологию, как вы захотите. Так, обратите внимание, как я сказал "принадлежит", и если вы хотите, то вместо этого можете представить это как: %% $article->owner %% (4:00) Т.е. "владелец" статьи, и тогда можете изменить %%user()%% на %%owner()%%. Или, если вы хотите использовать термин "автор" статьи и хотите получить автора статьи и всю информацию о нём, то вы можете исправить это на: %%$article->writer%%. Вы можете представить это так, как вы только пожелаете. В нашем случае, просто чтобы всё было чётко и ясно, я буду придерживаться термина "пользователь" (%%user%%). Просто знайте, что у вас есть полный контроль над этим. ОК, так что это всё выглядит действительно хорошо, что касается Laravel и Eloquent, но мы также должны подумать о структуре нашей таблицы. (4:30) В нашей базе данных у нас до сих пор нет никакой связи между статьёй и пользователем. Позвольте мне вам показать это. Если мы снова посмотрим на нашу миграцию, у нас есть заголовок, тело, и временные штампы, но у нас нет никакой секции для обозначения создателя этой статьи или соответствующего пользователя. И мы можем сделать это с помощью простых внешних ключей. Мы могли бы добавить один новый здесь вот так: %% $table->integer('user_id'); %% И таким образом, у нас есть крюк (hook), чтобы за него зацепиться. (5:00) Однако, я также добавлю ещё кое-что здесь: %% ->unsigned(); %% Думайте об этом, как о способе сказать: "Целое число должно быть положительным". Так что нечто вроде -4 здесь не будет работать. Число должно быть положительным. Далее, хотя то, что мы имеем здесь уже работает, но вы также можете захотеть настроить внешнее ограничение (foreign constraint). Например, подумайте о любом приложении, где, возможно, когда пользователь удаляет свой аккаунт, вы также захотите удалить все различные вещи, которые ему принадлежат. (5:30) В контексте нашего небольшого демо-блога, если пользователь удаляет свой аккаунт, хотим ли мы удалить какие-либо из статей, которые он написал? И ответ может быть "да" или "нет", в зависимости от того, что конкретно вы создаёте. Если ответ "да", то мы можем автоматизировать этот процесс. Когда пользователь удаляет свой аккаунт, то мы каскадом идём вниз и удаляем все статьи, которые принадлежали этому пользователю. (6:00) Вот как мы это изобразим. В самом низу, я скажу: %% $table->foreign('user_id') %% И это будет ссылаться, %%references()%%, на поле %%(t)id%%, но какой таблицы? Таблицы пользователей: %% $table->foreign('user_id') ->references('id') ->on('users'); %% И снова, обратите внимание, как в Laravel и в нашем строителе схем БД, вы просто пишете всё почти в точности так, как вы об этом и говорите. Наш внешний ключ здесь %%(t)user_id%%, который ссылается на поле идентификатора %%(t)id%% в таблице %%(t)users%%. Но дальше, как насчёт той идеи, когда пользователь удаляет свой аккаунт? (6:30) Ну, мы могли бы сказать: %% ->onDelete('cascade') %% Таким образом, когда пользователь удаляет свой аккаунт, каскадом идём вниз и удаляем все его статьи и любые другие соответствующие записи. ОК, это хорошо выглядит. И помните, поскольку мы это ещё не выложили в производство, и у нас здесь не производственная база данных, для таких сценариев совершенно нормально, если вы обновите вашу исходную миграцию. Просто откатите всё обратно и снова запустите. Однако, если у вас уже есть что-то в производстве, вы больше не сможете этого делать, потому что вы не можете избавиться от этих уже существующих данных. (7:00) Для таких случаев, вы создадите новую миграцию, чтобы добавить идентификатор пользователя и внешнее ограничение. ОК, мы обновили нашу миграцию, давайте теперь всё перезапустим. Если мы снова запустим %%(sh)php artisan%%, вы увидите, у нас тут есть различные команды для миграции. Вот та, которая нам нужна: %%(t)migrate:refresh%%. Мы начнём с отката всех миграций базы данных, и затем мы повторно перезапустим их. Всё понятно? (7:30) Итак: %%(sh) php artisan migrate:refresh %% Теперь, конечно, просто помните, что когда мы запустим эту команду, то все данные, которые вы храните локально, также сбросятся. Так что все записи в нашей таблице статей, конечно же будут удалены. Это ожидаемое поведение. Хорошо, таким образом мы откатили наши миграции обратно, и мы вновь запустили их. Хорошо. Теперь, чтобы это продемонстрировать, нам нужен какой-нибудь пользователь. И Laravel на самом деле даёт нам довольно хорошую аутентификацию и регистрацию по умолчанию. (8:00) Но я хочу посвятить этому целое видео. Так что в этом уроке, давайте быстро создадим пользователя. Мы будем использовать: %%(sh) php artisan tinker %% И: %% $user = new App\User %% И если я переключусь обратно к нашему редактору, то вот наша миграция для таблицы пользователей. Мы должны дать ей имя, электронную почту и пароль. %% $user->username = 'John Doe'; $user->email = 'john@example.com'; $user->password = 'password'; %% (8:30) Однако мы всегда будем хэшировать пароли. Это основа безопасности базы данных. Когда пользователь даёт вам свой пароль, вы никогда, никогда, никогда, никогда не должны сохранять его в текстовом виде. Вы всегда должны хэшировать его. Так что, конечно, Laravel предоставляет некоторые интересные возможности для этого. Например, вот так: %% $user->password = Hash::make('password'); %% Или же, в Laravel 5, у нас есть небольшая вспомогательная функция под названием %%bcrypt()%%, и она делает, в буквальном смысле, то же самое: %% $user->password = bcrypt('password'); %% (9:00) Это просто обёртка вокруг %%Hash::make()%%. ОК, так что давайте запустим это. И заметьте, для нас сгенерирован этот длинный хэш. Этого достаточно для ввода в таблицу. И, наконец, это всё хорошо, поэтому давайте сохраним нашего пользователя: %% $user->save(); %% И вот, у нас есть наш пользователь. Так что, если мы запросим первого пользователя из базы данных, и приведём к массиву: %% App\User::first()->toArray(); %% Мы должны получить его, и да, вот он. ОК, хорошо. Таким образом, у нас здесь есть фиктивный пользователь, теперь как бы мы могли создать статью и связать её с этим пользователем? (9:30) Что ж, позвольте мне показать вам, и мы будем здесь кратки. Так что в нашем %%ArticlesController%%, прямо здесь (в %%store()%%), мы будем сохранять новую статью. С практической точки зрения, в реальной жизни у вас была бы система аутентификации. И тогда вы бы просто установили идентификатор пользователя равным аутентифицированному пользователю, и вы могли бы выразить это вот так: %%Auth::user()%%. Это относится к человеку, который на настоящий момент вошёл в систему. Однако, как я уже сказал, мы ещё не настроили это. (10:00) На самом деле, мы сделаем это уже в следующем уроке. Пока же мы будем фальсифицировать это с пониманием того, что мы действительно исправим или настроим это в следующем уроке. ОК, так что давайте посетим нашу форму для статьи и (временно) я здесь скажу: %%(h) %% мы собираемся добавить скрытый элемент входных данных, который имеет идентификатор пользователя, с которым мы хотим связать статью. Мы можем сделать это так: %% {!! Form::hidden('user_id', 1) !!} %% (10:30) Мы хотим скрыть вводимый идентификатор пользователя и мы установим его в 1, потому что у нас есть только один пользователь. Таким образом, идентификатор 1 – то, что мы и хотим здесь. И этого будет достаточно, по крайней мере, для нашего первого прохода. Но всегда думайте о возможных проблемах безопасности. Возможно ли, что, когда человек заполняет эту форму, он может открыть средства разработки Chrome и изменить значение с 1 на 2? Тогда, при отправке формы, он создаст статью, которая будет привязана к совершенно другому человеку. (11:00) Это те вещи, о которых вы должны думать, и именно поэтому я специально делаю такое решение лишь временным. Мы точно не хотим, чтобы это было в реальном приложении. Но пока мы не создали нашу систему с аккаунтами, этого достаточно. ОК, давайте перейдём к модели %%Article%%, cделаем %%(t)id%% массово назначаемым. Опять же, временно! И давайте попробуем это в браузере. Итак, если мы откроем Chrome, давайте введём здесь %%(t)New Article%%, %%(t)the body%%, опубликовано сегодня. (11:30) И если мы добавим это, то, всё выглядит хорошо. И если мы теперь выберем это: %% App\Article::first()->toArray() %% (снова берём самый первый элемент и приводим к массиву). Обратите внимание на то, что мы на самом деле установили идентификатор пользователя к нашему %%(t)John Doe%%. Так что теперь мы можем проверить наши отношения и чтобы сделать это, мы можем остаться с %%(t)tinker%% до поры до времени. Так что давайте захватим нашего первого пользователя: %% $user = App\User::first(); $user->toArray(); %% ОК, у нас есть %%(t)John Doe%%. (12:00) Теперь я хочу сказать: "Дайте мне все статьи, которые %%(t)John Doe%% создал" %% $user->articles->toArray(); %% Ещё раз приведём к массиву, чтобы его можно было легко просмотреть. И готово. Вот так легко, мы вызываем %%$user->articles%% и получаем коллекцию всех статей, которые имеют идентификатор пользователя равным 1. Теперь я хочу убедиться, что вы это запомнили навсегда. Если мы вернемся к модели %%User%%, это доступно из-за вот этого метода здесь: %%articles()%%. И если вы задаетесь вопросом: "Это же метод, так почему мы не говорим: %%$user->articles()%%?". (12:30) И да, это немного сбивает с толку, я согласен. Однако, если мы запустим это, то вы увидите, что то, что вы получите взамен является объектом %%HasMany%%. И это позволяет нам продолжать формирование цепочки. Например, самый простой способ для меня чтобы вам это продемонстрировать, будет если я просто покажу вам оба результата. Когда мы сделали %%$user->articles%%, это дало нам коллекцию. Но когда мы делаем %%$user->articles()%% как метод, то чтобы получить тот же результат, вам придётся дополнительно вызвать %%->get()->toArray()%%. (13:00) Очень похоже на то, что мы сделали в нашем контроллере. Та же идея. Так что если мы так и сделаем, то это будет эквивалентно: %% $user->articles()->get()->toArray(); %% Всё понятно? Но когда вы используете это как метод, если вам нужно добавить некоторые дополнительные ограничения, то вы можете сделать так: %% $user->articles()->where('title', 'New Article'); %% Конечно же, это лишь вернёт вам ту же самую запись. Но для большого набора результатов, естественно, это не сработает. Тогда: %% ->get() %% (13:30) Потому что когда мы вызываем его как метод, то мы получаем коллекцию. И ещё раз, мы можем привести результат к массиву для просмотра: %% $user->articles()->where('title', 'New Article')->get()->toArrray(); %% Но когда мы делаем %%$user->articles%%, конечно, вы не можете этого сделать, потому что теперь, когда мы запускаем это, то мы получаем коллекцию. И мы пока что ещё не говорили о коллекциях. Я сделаю себе пометку – посвятить им полное видео, так как они действительно классные. Коллекция – это как массив на стероидах. Это то, как вы хотели бы взаимодействовать с набором элементов. (14:00) Но на данный момент мы отложим эту тему. Так что просто помните, что когда вы ссылаетесь на отношение, то вы на самом деле не вызываете его как метод. За кадром, Laravel все ещё как бы ждёт его. Или Eloquent ожидает его. И он сообразит: "О, я вижу, что вы пытаетесь здесь сделать. Вы хотите коллекцию для отношений". Всё понятно? Так что теперь, если я вернусь к %%Article%%, давайте рассмотрим противоположный пример. Скажем, у нас есть не пользователь, а статья. (14:30) И вместо этого я хочу сказать: "Дайте мне пользователя или владельца этой статьи". Давайте попробуем. ОК, на этот раз, давайте выберем какую-нибудь статью, скажем, мы просто схватим первую: %% $article = App\Article::first(); %% Хорошо. И теперь я могу сказать: %% $article->user->toArray() %% Это работает в обоих направлениях. И самое главное, что здесь нужно понять – метод на который вы ссылаетесь будет определять, какой в конечном счете будет сгенерирован запрос SQL. (15:00) Когда мы говорим, что статья принадлежит пользователю, мы на самом деле говорим, что таблица статей должна иметь какую-нибудь колонку вроде %%(t)user_id%%, и это будет нашим крюком, поскольку у нас есть отношение %%belongsTo()%%. Итак, чтобы закончить это видео, я хочу, чтобы вы подумали о некоторых из удобств которые вам здесь доступны. Если вы когда-нибудь захотите просмотреть все статьи для пользователя, то у вас может быть URI типа %%(t)laravel5.dev/users/JohnDoe/articles%%. Вы бы стали ожидать такой вызов, и тогда всё, что вам придётся сделать за кадром – это просто-напросто получить пользователя. (15:30) Итак: %% $user = App\User::where('username', 'John Doe')->first(); $user->toArray(); %% ОК, у нас есть наш пользователь %%(t)John Doe%%. Далее, мы хотим получить все статьи для пользователя. Как представлено здесь. ОК: %% $articles = $user->articles; $articles->toArray(); %% И теперь они у нас есть. (16:00) Наконец, в контроллере, у вас есть коллекции, так что просто передайте их в представление. И это будет нечто вроде: %% return view('articles.index', compact('articles')); %% (вы бы прошли через список статей для пользователя). Так что, ещё раз, очень чисто и очень просто.