Laravel. Новости. Советы. Уроки. /feed /images/logo_main.png 2017-06-16T16:38:00+03:00 <![CDATA[Вышла версия 5.4.27]> /blog/news/laravel-5.4.27 Вышла новая версия нашего любимого фреймворка Цель этой версии исправить баг, котрый появился в прошлой версии

Исправлено

  • Обновление таймстемпов при мягком удалении, только если они фактически используется (#19627)

Добавлено

  • Добавлен метод Collection::diffAssoc() (#19604)
]>
2017-06-16T16:38:00+03:00
<![CDATA[Вышла версия 5.4.26]> /blog/news/laravel-5.4.26 Добавлено
  • Добавлен метод Event::nextRunDate() (#19537, 09dd336)
  • Добавлен оператор <=> в список операторов построителя запросов (#19539)
  • Добавлен трейт Macroable в RequestGuard (#19569)

Изменено

  • Обновление атрибута updated_at при мягком удалении (#19538)
  • Список аргументов в Rule::in() и Rule::notIn() (#19555)
  • Поддержка проверки строк в именах заданий при использовании QueueFake (#19575)
  • Улучшена точность валидации соотношения размеров изображения (#19542)

Полный список изменений

]>
2017-06-14T16:14:00+03:00
<![CDATA[Поддержка автоматический регистрации пакетов в Laravel 5.5]> /blog/laravel-5.5/laravel-5.5-package-auto-discovery Как сейчас обычно выглядит подключение пакета Laravel?

1) Подключаем пакет через composer

composer require some/package

2) Регистрируем сервис-провайдер в config/app.php 3) Прописываем необходимые алиасы (фасады)

В Laravel 5.5 (при условии, что подключаемый пакет готов к этому) потребуется лишь шаг 1

Каким образом пакет должен быть подготовлен?

Просто в composer.json надо включить дополнительный секцию c перечнем сервис-провайдеров и фасадов для регистрации

"extra": {
….
+        "laravel": {
+            "providers": [
+                "Barryvdh\\Debugbar\\ServiceProvider"
+            ],
+            "aliases": {
+                "Debugbar": "Barryvdh\\Debugbar\\Facade"
+            }
         }
     }

А если мне это не нужно?

Если вам надо отключить автоматическую регистрацию пакета, необходимо прописать это у себя в composer.json в секции extra.laravel.dont-discover таким образом

]>
2017-06-14T16:04:00+03:00
<![CDATA[Вышла версия 5.4.24]> /blog/news/laravel-5.4.24 Что нового
  • Поддержка магических методов в контроллерых #19168
  • Добавлены ресурсы Gate #19124
  • Добавлен метод Request::routeIs() #19202, 26681eb
  • Добавлен метод Route::isName() #19227
  • Добавлена поддержка свободного указания имени поля в методе softDeletes() #19203

Теперь можно задать колонку, участвующую в «мягком удалении» при помощи вызова метода $table→softDeletes('left_at'); Раньше это имя всегда равнялось deleted_at `

  • Добавлен метод ManagesLayouts::getSection() #19213
  • Добавлен краткий метод Model::refresh() 19174

Теперь вместо $user = $user→fresh() Можно сделать $user→refresh() метод перегрузит все отношения, загруженные при создании модели.

  • Добавлен метод Container::forgetExtenders() #19269, 7c17bf5
  • Добавлен метод Filesystem::hash() #19256

Что изменилось

  • Проперти $sizeRules и $numericRules переехали из FormatsMessages в Validator dc7e7cb
  • Теперь можно вызывать метод Collection::times() без коллбека #19278

Что исправлено

  • Метод Container::makeWith() раньше не использовал параметры при определении интерфейсов #19178
  • Убрана валидация соеденения с  Memcached #19192
  • Исправлена позиция проверки bound() в методе Container::instance() #19207
  • Убрано наложение глобального окружения на фабрику при установке соединения #19258
  • Исправлен баг с созданием соединения в воркере очередей #19263
]>
2017-05-29T21:38:00+03:00
Eric L. Barnes <![CDATA[Zttp - обертка вокруг Guzzle, упрощающая жизнь]> /blog/laravel-packages/zttp-guzzle-wrapper Zttp - новый пакет от Adam Wathan является по сути оберткой вокруг Guzzle, созданный упростить синтаксис самых распространенных запросов. Вот например как выглядит POST запрос с дополнительными заголовками


$response = Zttp::withHeaders(['Fancy' => 'Pants'])->post($url, [
    'foo' => 'bar',
    'baz' => 'qux',
]);

$response->json();

В Guzzle этот запрос выглядел бы так


$client = new Client();
$response = $client->request('POST', $url, [
    'headers' => [
        'Fancy' => 'Pants',
    ],
    'form_params' => [
        'foo' => 'bar',
        'baz' => 'qux',
    ]
]);

json_decode($response->getBody());

Как видите, Zttp упрощает код запроса и автоматом возвращает JSON

Вот еще примеры:

POST запрос формы

$response = Zttp::asFormParams()->post($url, [
    'foo' => 'bar',
    'baz' => 'qux',
]);

PATCH запрос

$response = Zttp::patch($this->url('/patch'), [
    'foo' => 'bar',
    'baz' => 'qux',
]);

PUT запрос

$response = Zttp::put($this->url('/put'), [
    'foo' => 'bar',
    'baz' => 'qux',
]);

DELETE запрос

$response = Zttp::delete($this->url('/delete'), [
    'foo' => 'bar',
    'baz' => 'qux',
]);

Добавим заголовок Accept

$response = Zttp::accept('banana/sandwich')->post($url);

Без редиректа

$response = Zttp::withoutRedirecting()->get($url);

Остальные примеры можно посомтреть в этом тесте Пакет все еще находится в разработке. Следите за ним на GitHub

]>
2017-05-26T21:23:00+03:00
<![CDATA[Интерактивная команда make для Laravel 5.4]> /blog/laravel-packages/interactive-make-command Полезный пакет для склеротиков, вроде меня, которые вечно забывают сигнатуры команд. Интерактивная команда make для Laravel 5.4 сама распросит вас, что вы собираетсь создать.

Установка

Подключите в композере

composer require laracademy/interactive-make

Пропишите сервис-провайдер в config/app.php

'providers' => [
    // ...
    Laracademy\Commands\MakeServiceProvider::class,
],

Использование

Теперь просто запустите в консоли

php artisan make

Вот что получится

]>
2017-05-25T09:40:00+03:00
Zac Vineyard <![CDATA[Используем Google API вместе с Socialite]> /blog/tutorials/google-api-socialite Когда я начал проект, в котором пользователь должен был иметь возможность авторизации через Google эккаунт, я немедленно вышел на Laravel Socialite. Это официальный пакет Ларавел, но его возможности оказались ограничены только авторизацией, мне же было необходимо сделать больше. Помимо логина мне надо было получить список контактов Google авторизованного пользователя. В этой статье я расскажу как мне это удалось сделать, не отказываясь от Socialite.

Я подразумеваю, что у вас установлен чистый проект Laravel c пакетом Laravel Socialite. Если вы не знаете как это сделать, обратитесь к документации Socialite

Создаем приложение в Google API Консоли

Для начала надо создать приложение в в Google API Консоли. Найдите ссылку «Создать проект» вверху страницы проекта. Когда вы закончите создание нового проекта в Google API Console, необходимо будет скопировать себе 3 элемента информации о проекте: Google server key (ключ АПИ), client ID (идентификатор клиента) и app secret (ключ приложения). App secret будет доступен после создания проекта, server key и client ID можно найти в разделе «Учетные данные» консоли, если там будет пусто, то надо будет для начала создать новые учетные данные.

Когда все три ключа будут у вас, необходимо прописать их в .env файле

GOOGLE_SERVER_KEY=AIzaSyC_g8Uj5GGAqnPZaZAmlVMkUj0DXOVw0Z8 GOOGLE_CLIENT_ID=53500906325-ocfb3qbl0inpb249gnuir4988kn3ef52.apps.googleusercontent.com GOOGLE_APP_SECRET=YnceM3Bdn6JpboaFgc27B3Im GOOGLE_REDIRECT=http://localhost00/login/google/callback 

Установка клиента Google API

Для продолжения работы необходим будет пакет клиента АПИ гугла Просто подключим его через композер

composer require google/apiclient:^2.0

После того, как установка будет окончена, подключим клиент в нашем контроллере auth/LoginController.php Также придется подключать любой сервис, который бы будем использовать. В этом примере мы собираемся использовать Google’s People API для поиска контактов, нам потребуется сервис Google_Service_People, подключим и его в auth/LoginController.php

<?php

namespace App\Http\Controllers\Auth;

use Socialite;
use Google_Client;
use Google_Service_People;

Определим области видимости (scopes) API

В процессе установки пакета Socialite вы должны были добавить методы redirectToProvider() и handleProviderCallback(). в файл auth /LoginController.php. Убедитесь, что прописали нужные области видимости в методе redirectToProvider(). В данном примере мы будем запрашивать контакты пользователя, поэтому добавим в Google_Service_People::CONTACTS_READONLY в метод scopes объекта Socialite.

public function redirectToProvider()
{
  return Socialite::driver('google')
        ->scopes(['openid', 'profile', 'email', Google_Service_People::CONTACTS_READONLY])
        ->redirect();
}

Включим API

При любом использовании Google API, вам необходимо включить соответствующий раздел в консоли Google API. Перечень разделов находится под пунктом меню «Библиотека»

Google People API не показывается в списке популярных АПИ, поэтому его надо найти через окно поиска.

Включите Google People API для своего приложения.

Используем токен Socialite для клиента Google API

Laravel Socialite и клиент Google API имеют небольшие различия в структуре данных. Токен, который хранится в Socialite не соответствует структуре данных, которую требует Google API PHP клиент. Socialite отдает объект, а клиенту необходим массив в JSON.

В методе handleProviderCallback() класса auth /LoginController.php нам надо будет создать массив для гугло-клиента используя token, refreshToken и expiresIn параметров объекта Socialite. Затем сконвертируем все это в JSON и скормим в метод Google_Client::setAccessToken 

ublic function handleProviderCallback(Request $request)
{
    $user = Socialite::driver('google')->user();

    // Установим токен в  Google API PHP Client
    $google_client_token = [
        'access_token' => $user->token,
        'refresh_token' => $user->refreshToken,
        'expires_in' => $user->expiresIn
    ];

    $client = new Google_Client();
    $client->setApplicationName("Laravel");
    $client->setDeveloperKey(env('GOOGLE_SERVER_KEY'));
    $client->setAccessToken(json_encode($google_client_token));
}

После того, как мы установили токен в Google_Client, можно делать запросы к АПИ, которые мы включили и область видимость которых соответствует запрошенной.

public function handleProviderCallback(Request $request)
{
    $user = Socialite::driver('google')->user();

    // Установим токен в  Google API PHP Client
    $google_client_token = [
        'access_token' => $user->token,
        'refresh_token' => $user->refreshToken,
        'expires_in' => $user->expiresIn
    ];

    $client = new Google_Client();
    $client->setApplicationName("Laravel");
    $client->setDeveloperKey(env('GOOGLE_SERVER_KEY'));
    $client->setAccessToken(json_encode($google_client_token));
    // Запросим контакты пользователя
    $service = new Google_Service_People($client);

    $optParams = array('requestMask.includeField' => 'person.phone_numbers,person.names,person.email_addresses');
    $results = $service->people_connections->listPeopleConnections('people/me',$optParams);

    dd($results);
}

Важное замечание по использованию Google’s People API

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

Обновление токенов

Socialite должен разруливать обновление токена, если он протухает. Если токен истек, необходимо сделать новый запрос при помощи Socialite, затем передать новый токен клиенту Google API, как было показано выше.

Попробуем на практике

Если вы запустите команду  php artisan serve  в консоли своей машины. Можно перейти по адресу http://localhost00/login/google  и протестировать только что написанный код. Вам будет предложено войти в свой Google эккаунт и предоставить запрошенные разрешения (лист контактов и учетные данные). После того как вы кликнете «Разрешить» Лист ваших контактов должен отобразиться на экране в сыром виде, сдампленный функцией dd()

Что дальше

Код выше, конечно, очень простой пример использования Google API вместе c Socialite. Токен Socialite можно сохранить в базу или сессию и использовать в приложении. Функционал уже можно развивать исходя из ваших потребностей.

]>
2017-05-18T16:47:00+03:00
Eric L. Barnes <![CDATA[Новые функции-помощники в Laravel 5.5: throw_if и throw_unless]> /blog/laravel-5.5/throw_if-throw_unless В Laravel 5.5 будут доступны две новые вспомогательные функции throw_if и throw_unless. Обе предназначены для более удобного выбрасывания исключения.

«Как и другие функции-помощники, throw_if и throw_unless помогают делать код чистым и выразительным» говорит TJ Miller «В частности эти две функции могут заменить условный блок одной строкой кода. Это довольно круто.»

Давайте посмотрим на них более детально

throw_if

Делает именно то, что содержит в названии: если первый параметр - true, выбрасывает исключение. Вот пример использования:

$foo = false;
throw_if($foo, new BarException('Foo is false'));
// ИЛИ
throw_if($foo, BarException::class, 'Foo is False'); 

А вот ее исходный код для справки

function throw_if($boolean, $exception, $message = '')
{
    if ($boolean) {
        throw (is_string($exception) ? new $exception($message) : $exception);
    }
}

throw_unless

Это обратная функция, действует с точностью до наоборот. Если первый параметр - false, выбрасывает исключение.

$foo = true;
throw_unless($foo, new BarException('Foo is True'));
// ИЛИ
throw_unless($foo, BarException::class, 'Foo is True');

А вот ее исходный код:

function throw_unless($boolean, $exception, $message)
{
    if (! $boolean) {
        throw (is_string($exception) ? new $exception($message) : $exception);
    }
}
]>
2017-05-17T15:20:00+03:00
Amit Gupta <![CDATA[Как отправить email при возникновении ошибки (исключения) в приложении]> /blog/tutorials/email-on-error-exceptions Вы создали свое приложение на Laravel, залили все на сервер клиента. И все шло отлично, пока у заказчика не возникли проблемы из-за бага в вашем коде. Он просто закрыл страницу, ошибка повторилась еще и кучи народа, прежде чем вы о ней узнали и исправили.

Знакомая ситуация? А что если бы вы получили сообщение об ошибке по email или другому каналу сразу же при ее возникновении. В Laravel это очень просто реализовать и сейчас мы узнаем как это сделать.

В Laravel все исключения обрабатываются классом App\Exceptions\Handler Этот класс содержит два метода report и render. Нас сейчас интересует только метод report. Он используется для записи сообщения о ошибке в лог или отправки во внешний сервис типа Bugsnag или Sentry. По умолчанию, метод report просто передает исключение в базовый класс, где оно записывается в лог. Однако здесь можно отправить сообщение разщработчику

/**
 * Отправляем отчет об исключении.
 *
 * Здесь как раз то место, где можно отправить email.
 *
 * @param  \Exception  $exception
 * @return void
 */
public function report(Exception $exception)
{
    if ($this->shouldReport($exception)) {
        $this->sendEmail($exception); // отправляем email
    }

    return parent::report($exception);
}

/**
 * Отправляем email разработчику с уведомлением об ошибке.
 *
 * @param  \Exception  $exception
 * @return void
 */
public function sendEmail(Exception $exception)
{
    // отправка email
}

В коде мы используем метод shouldReport чтобы игнорировать исключения, которые представлены в списке $dontReport обработчика исключений.

Каждый тип сообщений, отправляемый приложением представлен в Laravel своим mailable классом. Создать такой класс можно при помощи команды

 php artisan make:mail ExceptionOccured

Эта команда создаст класс ExceptionOccured в директории app/Mail

Однако, просто уведомления не достаточно. Надо отправить полный стек исключения. Для этого воспользуемся компонентом Symfony Debug

public function sendEmail(Exception $exception)
{
    try {
        $e = FlattenException::create($exception);

        $handler = new SymfonyExceptionHandler();

        $html = $handler->getHtml($e);

        Mail::to('developer@gmail.com')->send(new ExceptionOccured($html));
    } catch (\Exception $ex) {
        dd($ex);
    }
}

Не забудьте добавить вверху файла

use Mail;
use Symfony\Component\Debug\Exception\FlattenException;
use Symfony\Component\Debug\ExceptionHandler as SymfonyExceptionHandler;

Обратите внимание, мы завернули отправку сообщения в блок try-catch чтобы избежать бесконечного цикла, если при отправке сообщения возникнет исключение.

Вот так выглядит класс ExceptionOccured

<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;

class ExceptionOccured extends Mailable
{
    use Queueable, SerializesModels;

    /**
     * The body of the message.
     *
     * @var string
     */
    public $content;

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct($content)
    {
        $this->content = $content;
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->view('emails.exception')
                    ->with('content', $this->content);
    }
}

Добавьте следующий код в шаблон  emails.exception

{!! $content !!}

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

]>
2017-05-15T13:37:00+03:00
Eric L. Barnes <![CDATA[Небольшие улучшения внешнего вида сообщений об ошибках в Laravel 5.5]> /blog/laravel-5.5/laravel-5-5-error-views В Laravel 5.5 улучшен дизайн дефолтных страниц сообщений об ошибках. Теперь эти страницы расширяют лейаут errors::layout и в них внесены некоторые внешние улучшения. Вот как выглядит старая страница ошибок 50*

А вот ее внешний вид в Laravel 5.5

Изменение сообщений об ошибках по умолчанию

Также как и раньше вы легко можете изменить представления страниц с сообщением об ошибке при помощи создания своих шаблонов в директории resources/views/errors. Необходимо назвать ваш шаблон по коду ошибки. Например для ошибки 500, шаблон должен называться resources/views/errors/500.blade.php

Внутри шаблоны можно модифицировать следующие секции

@extends('errors::layout')

@section('title', 'Ошибка')

@section('message', 'Ой, у нас ошибка, но мы ее уже устраняем, не уходите далеко :)')

Родительский лейаут errors::layout берется из недр фреймворка И вы можете испольщовать его, либо создать свой собственный.

По умолчанию Ларавел включает в себя следующие шаблоны ошибок

  • 404.blade.php
  • 419.blade.php
  • 500.blade.php
  • 503.blade.php

И если вам интересно, как же фреймворк выбирает шаблон для отображения страницы ошибки, посмотрите внутрь метода renderHttpException

Первая часть — это вызов метода replaceNamespace  который дает указание для начала заглянуть внутрь директории resources/views затем брать шаблон уже внутри фреймворка.

view()->replaceNamespace('errors', [
    resource_path('views/errors'),
    __DIR__.'/views',
]);

Затем метод возвращает представление согласно коду ошибки

return response()->view("errors::{$status}"

Стоит также упомянуть, что можно также задать директории для поиска шаблонов при помощи конфигурационного файла config/views.php

'paths' => [
    realpath(base_path('resources/views/new-design')),
    realpath(base_path('resources/views')),
],

Теперь Laravel сначала будет искать шаблон в папке new-design, а если не найдет — отправится в директорию views.

]>
2017-05-14T22:14:00+03:00
Joe Dixon <![CDATA[В Laravel 5.5 появится поддержка отчетов в собственных исключениях]> /blog/laravel-5.5/custom-exception-reporting В Laravel 5.5 можно будет при создании собственного исключения определить в нем метод для отправки отчета. Фреймворк при обработке исключения в классе Illuminate\Foundation\Exceptions\Handler будет искать метод с именем report и, при его наличии, выполнит его

if (method_exists($e, 'report')) { return $e->report(); }

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

В предыдущих версиях Laravel, этого можно было достигнуть при помощи редактирования метода report класса App\Exceptions\Handler и обработки исключения по имени

public function report(Exception $exception)
{
    if ($exception instanceof MyException) {
        // выполняем требуемые действия
    }

    return parent::report($exception);
}

Как вы понимаете, с ростом приложения, метод может разростись, и его станет трудно поддерживать, а это небольшое изменение в Laravel 5.5 упрощает жизнь.

]>
2017-05-14T17:55:00+03:00
<![CDATA[Новая команда миграции]> /blog/laravel-5.5/new-migration-command В Laravel 5.5 появится новая команда миграции migrate:fresh. В более ранних версиях существует команда migrate:refresh которая откатывает все миграции и накатывает их снова.

Команда migrate:fresh похожа на нее, но в отличии от refresh , которая откатывает миграции при помощи методов down каждой миграции, новая команда migrate:fresh удаляет все таблицы в базе данных и выполняет все миграции с нуля на чистую базу.

Подобное будет полезно особенно тем, кто не пишет методы down в миграциях.

]>
2017-04-06T13:31:00+03:00
Joe Dixon <![CDATA[Система регистрации пользователей по приглашениям на Laravel]> /blog/tutorials/user-invitation-system Регистрация пользователей по приглашениям - довольно распространенная задача.

В качестве примера, представьте себе контентый сайт, на котором определённые авторы должны иметь возможность публиковать статьи. Можно конечно же создавать пользователей через админку и потом высылать им пароли. Открытым текстом, почтой, что не есть безопасно.

А можно выслать им инвайт и пускай они сами придумают себе удобный пароль и заполнят персональные данные.

Рассмотрим как это сделать.

Введение

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

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

Будем использовать чистую установку Laravel 5.4, вам необходимо будет сконфигурировать настройки базы данных и почтового сервера.

Миграции

Вместе с дефолтным проектом Laravel в комплекте идет миграция для создания таблицы пользователей.

Для данного примера, мы уберем поля «name» и «password»:

public function up()
{
    Schema::create('users', function (Blueprint $table) {
        $table->increments('id');
        $table->string('email')->unique();
        $table->rememberToken();
        $table->timestamps();
    });
}

Также нам необходимо создать миграцию для наших инвайтов. В консоли, в папке с проектом выполните команду php artisan make:migration create_invites_table.

Эта команда создаст новый файл в директории database/migrations . В таблице будут поля для первичного ключа, email адреса и уникальный токена

public function up()
{
    Schema::create('invites', function (Blueprint $table) {
        $table->increments('id');
        $table->string('email');
        $table->string('token', 16)->unique();
        $table->timestamps();
    });
}

public function down()
{
    Schema::drop('invites');
}

Теперь выполним миграции при помощи команды php artisan migrate.

Наша база готова

Модели

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

Как и в случае с миграциями, Laravel имеет модель User из коробки, так что создавать ее не потребуется.

Для инвайтов создадим модель при помощи команды php artisan make:model Invite.

Посмотрите в папку app своего проекта, в ней должен появиться новый файл с нашей моделью.

Все, что осталось сделать с этой моделью — прописать заполняемые поля. Это позволит массово присваивать проперти модели.

protected $fillable = [
    'email', 'token',
];

Роуты

Для этого урока нам потребуется прописать три маршрута (роута):

  • Показать форму приглашения пользователя
  • Обработать отправку формы
  • Принять приглашение

В файле app/routes/web.php добавьте следущее:

Route::get('invite', 'InviteController@invite')->name('invite');
Route::post('invite', 'InviteController@process')->name('process');
// {token}  обязательный параметр, который будет передан в метод контроллера
Route::get('accept/{token}', 'InviteController@accept')->name('accept');

Контроллер

Внимательный читатель, возможно уже заметил, что при описании маршрутов мы ссылались на контроллер InviteController для обработки запросов.

Этот контроллер также можно создать из консоли php artisan make:controller InviteController.

Откройте app/Http/Controllers/InviteController.php и определите следующие методы:

public function invite()
{
    // Покажем форму для ввода email пользователя для приглашения
}

public function process()
{
    // Обработаем отправку формы, пошлем приглашение 
}

public function accept($token)
{
    // Здесь найдем пользователя по токену из URL
}

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

Бизнес логика

Пройдемся по каждому методу, который создали ранее

invite()

Все просто до безобразия, просто вернем представление с формой, в которую можно ввести email для приглашения.

public function invite()
{
    return view('invite');
}

Создадим файл resources/views/invite.blade.php. Это представление будет содержать нашу форму:

// будем использовать именованный роут на случай смены его  URL, 
<form action="{{ route('invite') }}" method="post">
    {{ csrf_field() }}
    <input type="email" name="email" />
    <button type="submit">Отправить приглашение</button>
</form>

process()

Здесь будет находится львиная доля решения нашей задачи.

Во-первых, нам необходимо уведомить пользователя, что его пригласили зарегистрироваться. Для этого будем использовать mailables Laravel.

Для начала, нам необходимо создать «отправляемый» (mailable) класс. В консоли выполните команду php artisan make:mail InviteCreated.

Эта команда создаст новый класс InviteCreated в директории app/Mail . Откройте его и модифицируйте конструктор, чтобы он принимал модель Invite и присваивал ее в публичную проперти.

use App\Invite;

public function __construct(Invite $invite)
{
    $this->invite = $invite;
}

Для отправки почты, определим метод build следующим образом.

public function build()
{
    return $this->from('you@example.com')
                ->view('emails.invite');
}

Теперь надо создать, упомнятый в методе шаблон, создадим файл resources/views/emails/invite.blade.php с очень простым содержанием:

<p>Привет,</p>

<p>Кто-то пригласил вас зарегистрироваться .</p>

<a href="{{ route('accept', $invite->token) }}">Жамка сюда</a> для активации!

Я слышу, как многие голосят - «Откуда у нас доступ к переменной $invite внутри шаблона!» А вот откуда - Laravel автоматически делает доступными в шаблонах все публичные проперти из отправляемого (mailable) класса.

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

use App\Invite;
use App\Mail\InviteCreated;
use Illuminate\Support\Facades\Mail;

...

public function process(Request $request)
{
    // валидация входящих данных

    do {
        //сгенерируем рандомную строку при помощи функции помощника Laravel  `str_random`
        $token = str_random();
    } // Проверим, нет ли уже такого токена, если есть сгенерим заново
    while (Invite::where('token', $token)->first());
    //создадим запись приглашения
    $invite = Invite::create([
        'email' => $request->get('email'),
        'token' => $token
    ]);

    // Отправим инвайт
    Mail::to($request->get('email'))->send(new InviteCreated($invite));

    // сделаем редирект обратно
    return redirect()
        ->back();
}

Отлично! Наше приглашение создано, пользователь уведомлен.

accept()

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

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

Помните, что токен из урл будет передан в качестве параметра в метод контроллера.

use App\User;
use App\Invite;
use App\Mail\InviteCreated;
use Illuminate\Support\Facades\Mail;

...

public function accept($token)
{
    // Найдем приглашение
    if (!$invite = Invite::where('token', $token)->first()) {
        // если инвайт не существует, можно сделать что-то более элегантное, чем то :)
        abort(404);
    }

    // Создадим пользователя с данными из инвайта
    User::create(['email' => $invite->email]);

    // удалим инвайт, чтобы им нельзя было воспользоваться снова
    $invite->delete();

    // здесь необходимо будет «залогинить» пользователя, сделать редирект в личный кабинет, мы же просто выведем надпись.

    return 'Отлично! Пользователь зарегистрирован!';
}

Проверим, как работает!

Перейдите по адресу формы приглашения ( http://vashdomen.ru/invite), введите email человека, которого хотите пригласить и отправьте форму.

Посомтрите на таблицу с приглашениями в своей БД, там должна быть новая запись с адресом и уникальным токеном. Проверьте ящик, на который было выслано приглашение: должно быть письмо с ссылкой для активации эккаунта.

Перейдите по этой ссылке, вы должны увидеть надпись «Отлично! Пользователь зарегистрирован!» Проверьте БД, запись с приглашением должна исчезнуть и должна появиться запись с созданным пользователем.

Заключение

Отличная работа! Вы успешно создали систему приглашений пользователей.

Хотя это и упрощенный пример, основа заложена приличная.

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

]>
2017-04-02T13:37:00+03:00
<![CDATA[Вышел Laravel 5.4]> /blog/news/laravel-5-4-released Вышла новая версия нашего любимого фреймворка

Полный список нововведений (на английском)

Руководство по обновлению (на английском)

На русском:

Что нового в Laravel 5.4 (часть 1)

Краткий обзор нововведений в Laravel 5.4

]>
2017-01-24T20:34:00+03:00
<![CDATA[Что нового в Laravel 5.4 (часть 1)]> /blog/news/Laravel-5.4-whats-new Совсем скоро состоится релиз Laravel 5.4. Посмотрим на некоторые из новых фич, которые предложит нам новая версия.

Laravel 5.4 включает в себя 2 новых Middleware

Trim Strings Middleware

Как можно догадаться по названию , этот посредник выполняет trim() на каждое значение в запросе.

Подключается при помощи добавления в файл App/Kernel.php следующего кода:

protected $middleware = [
    \Illuminate\Foundation\Http\Middleware\TrimStrings::class,

ConvertEmptyStringsToNull Middleware

Также посредник с говорящим названием. Как и посредник, описанный выше, работает с реквестом, а именно: трансформирует все пустые значения в Null

Автоматические фасады

В Laravel 5.4 появляется функционал, позволяющий создать фасад любого класса на лету.

Если вы не знакомы с фасадами, вот краткая выдержка из документации

Фасады предоставляют “статичный” интерфейс к классам, которые доступны в сервис-контейнере приложения. Laravel из коробки включает в себя фасады к практически всем своим основным функциям. Фасады Laravel служат в качестве “статичных прокси” к нижележащим классам внутри сервис-контейнера, предоставляют очень удобный, лаконичный интерфейс в тоже время сохраняют гибкость и возможность использования в тестах, в отличии от традиционных статичных методов.

Подробнее о фасадах можно прочесть в документации

Пример использования автоматического фасада

namespace App;

class Zonda 
{
    public function zurf()
    {
        return ‘Zurfing’;
    }
}

Затем где-нибудь в роутинге или контроллере:

use Facades\ {
    App\Zonda
};

Route::get('/', function () {
    return Zonda::zurf();
});

Laravel Elixir будет переименован в Laravel Mix

Следующая версия Laravel Elixir переписана c Gulp на Webpack, и такое значительное изменение дало повод сменить имя пакету.

Документация будет доступна к моменту выхода Laravel 5.4

Продолжение следует...

]>
2017-01-22T13:45:00+03:00
Eric L. Barnes <![CDATA[Отправка сообщений в Telegram при помощи каналов уведомлений (Notification Channels)]> /blog/tutorials/telegram-notification-channels Уведомления Laravel незаменимы, когда надо отправить сообщение по разным каналам. Согласно документации, первичное предназначение для них - отправка небольших информационных уведомлений пользователям о каких-либо событиях в приложении. Например, если вы разрабатываете биллинг, вам возможно понадобится отправить сообщение “Счет оплачен” посредством канала электронной почты или SMS.

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

Несколько месяцев назад я открыл группу в Telegram под названием Laravel News. Раньше уведомления в ней о новых статьях я постил бы вручную. Это довольно ненадежно, как вы можете догадаться, легко можно забыть и т. п. Я решил это дело автоматизировать и мне на помощь пришел пакет Telegram Notification Давайте рассмотрим как это работает.

Создаем бота для Telegram

Прежде, чем мы сможем отправить сообщение в комнату, нам необходимо создать бота. Для начал отправим сообщение пользователю @BotFather, выглядит это примерно так:

Вы: /newbot
BotFather: Alright, a new bot. How are we going to call it? Please choose a name for your bot.

Вы: MyTestBot
BotFather: Good. Now let's choose a username for your bot. It must end in bot. Like this, for example: TetrisBot or tetris_bot.

Вы: MyNewBot
BotFather: Done! Congratulations on your new bot. ... Use this token: xxxxxx 

Скопируйте полученный токен, позже пропишем его в .env

Добавим бота себе в комнату

Откройте свой чат, перейдите в settings -> administrators и добавьте бота, которого только что создали, в группу. Это даст боту возможность постить в общий чат.

Установка канала уведомлений Telegram

Установка мало чем отличается от установки обычного Laravel пакета. Для начала, подключим пакет :

composer require laravel-notification-channels/telegram

Пропишем провайдер в app.php:

'providers' => [
    ...
    NotificationChannels\Telegram\TelegramServiceProvider::class,
],

Добавим конфиг:

// config/services.php
...
'telegram-bot-api' => [
    'token' => env('TELEGRAM_BOT_TOKEN', 'YOUR BOT TOKEN HERE')
],

Откроем .env и пропишем в нем наш, полученный ранее токен:

TELEGRAM_BOT_TOKEN=1234:232jkl42l4j23kl

Создаем класс уведомлений

В моем случае, мне необходимо отправлять уведомления, когда опубликован новый пост на сайте, поэтому я создаю уведомление PostPublished при помощи artisan:

php artisan make:notification PostPublished

Теперь откройте файл и отредактируйте метод via для использования канала TelegramChannel:

public function via($notifiable)
{
    return [TelegramChannel::class];
}

Затем создайте метод toTelegram со следующим содержанием:

public function toTelegram($post)
{
    return TelegramMessage::create()
        ->to('@laravelnews')
        ->content($post->title.' https://laravel-news.com/'. $post->uri);
}

В нашем случае он посылает сообщение в комнату @laravelnews содержащую заголовок и ссылку на пост.

Добавляем трейт notifiable

Уведомления могут отправляться следующими способами: через фасад Notification или при помощи трейта Notifiable. Фасад рекомендуется использовать, если вам необходимо отправить уведомление нескольким уведомляемым сущностям, например коллекции пользователей.

В данном примере мы оперируем одной сущностью, поэтому я добавил трейт к модели Post .

use Illuminate\Notifications\Notifiable;
class Post extends Model
{
    use Notifiable;

Отправляем первое уведомление

Теперь, когда все настроено, самое время отправить первое уведомление. Создайте один экземпляр класса и запустите уведомление :

$post = \App\Post::find(1);
$post->notify(new \App\Notifications\PostPublished());

И если все верно, увидите в своем канале Telegram отправленное сообщение.

]>
2016-12-13T20:43:00+03:00
<![CDATA[Вышел Laravel v5.3.25 ]> /blog/news/laravel-v5-3-25 30 ноября вышел Laravel v5.3.25, эта версия включает в себя два новых правила валидации, возможность устанавливать права на файлы в Filesystem, и гибкий интерфейс для создания SlackMessageAttachmentField

Новые правила валидации

Два новых правила валидации появились в v5.3.25 before_or_equal и after_or_equal которые позволяют сравнить значение даты с некоторой заданной. Вот пример проверки старше ли человек 21 года:

$this->validate($request, [
    'birthday' => 'required|date|before_or_equal:1995-11-30',
]);

Обратный пример, младше 18:

$this->validate($request, [
    'birthday' => 'date|after_or_equal:1998-11-30',
]);

Права на файлы в Filesystem

Filesystem теперь имеет функцию установки и получения прав на файл:

$filesystem->chmod('file.txt'); // возвращает значение chmod
$filesystem->chmod('file.txt', 0777); // chmod 777 file.txt

Интерфейс построения SlackMessageAttachmentField

Slack поддерживает более развернутую форму аттачментов, в том время как Laravel ранее мог поддерживал только «краткий» стиль. С появлением нового функицонала, теперь можно указывать все поля, которые поддерживает Slack:

return (new SlackMessage)
    ->content('Content')
    ->attachment(function ($attachment) {
        $attachment
            ->title('Laravel', 'https://laravel.com')
            ->content('Attachment Content')

            //построим поле с аттачментом
            ->field(function($attachmentField) {
                $attachmentField
                    ->title('Special powers')
                    ->content('Zonda')
                    ->dontDisplaySideBySide();
            });

            //Добавим вручную
            ->field('Project', 'Laravel')
    });
]>
2016-12-02T13:41:00+03:00
Eric L. Barnes <![CDATA[Автоматическое создание Твитов при помощи уведомлений Laravel]> /blog/tutorials/send-tweets-laravel-notifications Одна из самых замечательных функций, появившихся в Laravel 5.3 – уведомления (Notifications), которые позволяют отправить небольшие сообщения через широкий ряд сервисов.

В ядро Laravel включена поддержка Email, Slack, и Nexmo, но сервисы можно дополнить как своими так и созданными сообществом .

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

Установка пакета Twitter

Для начала установим сам пакет :

composer require laravel-notification-channels/twitter

Затем пропишем провайдер в config/app.php

...
'providers' => [
    ...
     NotificationChannels\Twitter\TwitterServiceProvider::class,
],
…

Далее создадим новое приложение в Twitter и пропишем его параметры в config/services.php и в .env:

...
'twitter' => [
    'consumer_key'    => getenv('TWITTER_CONSUMER_KEY'),
    'consumer_secret' => getenv('TWITTER_CONSUMER_SECRET'),
    'access_token'    => getenv('TWITTER_ACCESS_TOKEN'),
    'access_secret'   => getenv('TWITTER_ACCESS_SECRET')
]
…

Подсказка: параметры access_token и access_secret находятся на закладке “Ключи и токены” (Keys and Access Tokens) на странице вашего Twitter приложения.

Публикуем твиты

В моем случае у меня есть модель Post которая и будет использоваться для публикации в твиттере, но вы может использовать любую свою модель. Просто добавьте к ней трейт Notifiable:

class Post extends Model
{
    use Notifiable;

Затем создайте класс уведомления:

php artisan make:notification PostPublished

Откройте этот файл и отредактируйте метод via для публикации твита. Вот что у меня получилось :

<?php

namespace App\Notifications;

use Illuminate\Notifications\Notification;
use NotificationChannels\Twitter\TwitterChannel;
use NotificationChannels\Twitter\TwitterStatusUpdate;

class PostPublished extends Notification
{
    public function via($notifiable)
    {
        return [TwitterChannel::class];
    }

    public function toTwitter($notifiable) {
        return new TwitterStatusUpdate('You should follow @laravelnews https://laravel-news.com/');
    }
}

Для отправки уведомления необходимо просто взять инстанс Post и вызвать один метод:

$post = Post::find(1);
$post->notify(new PostPublished());

В качестве завершения нам надо исправить метод toTwitter в классе PostPublished чтобы публиковать нужные нам данные.

public function toTwitter($post) {
    return new TwitterStatusUpdate($post->title .' https://laravel-news.com/'. $post->uri, [$post->featured_image]);
}

Модель Post передается в этот метод, затем я просто беру ее заголовок, урл и передаю изображение в качестве второго параметра.

Вот и все !

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

]>
2016-11-27T14:54:00+03:00
Rachid Laasri <![CDATA[Делаем простой поиск при помощи Laravel Scout и Vue.js]> /blog/tutorials/build-search-functionality-with-laravel-scout-and-vue-js Сегодня посмотрим, как сделать простой поиск при помощи Laravel Scout и Vue.js. На случай, если вы не знаете, что такое Laravel Scout, вот отрывок из документации Laravel:

Laravel Scout представляет собой простое, основанное на драйверах, решение по внедрению полнотекстового поиска по моделям Eloquent. При помощи “наблюдателей” за моделями, Scout автоматически поддерживает актуальность поискового индекса при изменении записей Eloquent.

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

На текущий момент Scout поставляется с драйвером Algolia; однако написать собственный драйвер довольно просто, и можно свободно расширять Scout собственными реализациями поиска.

Прежде чем погрузимся в код, посмотрим что в итоге должно получиться

Установка Laravel:

От переводчика: честно не знаю, зачем в каждой статье автор считает необходимым начать с этого :)

Выполним следующую команду в терминале:

composer create-project --prefer-dist laravel/laravel search

После выполнения команды, удостоверьтесь, что директории storage и bootstrap/cache открыты на запись серверу.

Вот что вы должны увидеть в браузере после успешной установки:

Конфигурация базы данных

Для этого примера я буду использовать SQLite, можете прописать у себя MySQL или любую другую БД.

DB_CONNECTION=sqlite

Если не указать имя базы данных, по умолчанию будет использоваться database/database.sqlite.

Модели и миграции:

Для нашего небольшого приложения будем использовать только одну модель Product и таблицу products.

Давайте их создадим:

php artisan make:model Product -m

Флаг -m дает указание команде artisan make:model создать миграцию.

Опишем следующие поля:

class CreateProductsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('products', function (Blueprint $table) {
            $table->increments('id');
            $table->string('title');
            $table->string('image');
            $table->integer('price');
            $table->text('description');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('products');
    }
}

Сохраните файл и запустите миграцию php artisan migrate

Тестовые данные:

При помощи фабрик моделей (Model Factories) мы заполним таблицу данными для тестов. Создадим файл database/factories/ModelFactory.php следующего содержания:

$factory->define(App\Product::class, function (Faker\Generator $faker) {
    return [
        'title' => $faker->sentence(),
        'image' => 'http://loremflickr.com/400/300?random='.rand(1, 100),
        'price' => $faker->numberBetween(3, 100),
        'description' => $faker->paragraph(2)
    ];
});

Наша фабрика готова, давайте заполним таблицу данными. Запустите команду php artisan tinker и затем factory(App\Product::class, 100)→create();. Записей можно создать сколько угодно, 100 меня устраивает.

От переводчика: Можно добавить вызов фабрики в миграцию

Маршруты и контроллеры:

Нам понадобятся следующие два маршрута:

  • GET : / - главная страница приложения. В файле routes/web.php:
Route::get('/', function () {
    return view('home');
});

Можно использовать и контроллер, но для такой мелкой операции считаю это излишним.

  • GET : api/search - этот маршрут будет обрабатывать результаты поиска.

routes/api.php

Route::get('/search', [
    'as' => 'api.search',
    'uses' => 'Api\SearchController@search'
]);

Для создания класса SearchController, просто запустите: php artisan make:controller Api\SearchController. Если вы не знали, то при указании неймспейса Laravel создаст директорию автоматически и поместит созданный контроллер в нее.

class SearchController extends Controller
{
    public function search(Request $request)
    {
        // we will be back to this soon!
    }
}

Установка Laravel Scout:

Установка и конфигурация Laravel Scout тривиальна. Пакет можно установить при помощи команды composer require laravel/scout. Когда процесс завершится, подключите сервис-провайдер ScoutServiceProvider в массив провайдеров в config/app.php:

Laravel\Scout\ScoutServiceProvider::class,

Затем необходимо опубликовать конфигурационные файлы пакета при помощи команды vendor:publish. Эта команда опубликует файл scout.php внутри директории config :

php artisan vendor:publish –provider="Laravel\Scout\ScoutServiceProvider"

И в качестве заключительного шага добавьте трейт Laravel\Scout\Searchable в модель Product для возможности поиска по ней:

<?php

namespace App;

use Laravel\Scout\Searchable;
use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    use Searchable;
}

Экспорт данных в Algolia

Теперь, когда Laravel Scout установлен, самое время установить драйвер Algolia

composer require algolia/algoliasearch-client-php

Далее необходимо экспортировать в Algolia все наши тестовые данные, которые мы сгенерили выше.

На сайте Algolia необходимо создать эккаунт ( бесплатный).

После регистрации на странице https://www.algolia.com/api-keys скопируйте ключи Application ID и Admin API Key и добавьте их в файл .env таким образом:

ALGOLIA_APP_ID=J4YK3E00YT
ALGOLIA_SECRET=9088241f69034ee7db231e67bc2cb0b4

Запустите команду

php artisan scout:import "App\Product"

Если вы все сделали верно, увидите примерно такое сообщение, которое означает, что все данные из таблицы products скопированы на сервер Algolia.

Удостовериться в этом можно, зайдя на страницу Indices своего эккаунта.

Пишем метод поиска:

Вернемся в наш контроллер app\Http\Controllers\Api\SearchController.php.

    /**
     * Поиск по таблице products .
     *
     * @param  Request $request
     * @return mixed
     */
    public function search(Request $request)
    {
        // Определим сообщение, которое будет отображаться, если ничего не найдено 
        // или поисковая строка пуста
        $error = ['error' => 'No results found, please try with different keywords.'];

        // Удостоверимся, что поисковая строка есть
        if($request->has('q')) {

            // Используем синтаксис Laravel Scout для поиска по таблице products.
            $posts = Product::search($request->get('q'))->get();

            // Если есть результат есть, вернем его, если нет  - вернем сообщение об ошибке.
            return $posts->count() ? $posts : $error;

        }

        // Вернем сообщение об ошибке, если нет поискового запроса
        return $error;
    }

Не забудьте импортировать модель Product при помощи use App\Product; Теперь при переходе по адресу http://your.local.domain/api/search?q=code вы должны увидеть JSON представление данных.

Клиентская часть:

В рамках этого урока мы особо не будем заострять внимание на дизайне. Вот шаблон, который мы будем использовать. Сохраните его в resources/home.blade.php.

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <link rel="stylesheet" href="/">
        <title>Search with Laravel Scout and Vue.js!</title>
    </head>
    <body>
        <div class="container">
            <div class="well well-sm">
                <div class="form-group">
                    <div class="input-group input-group-md">
                        <div class="icon-addon addon-md">
                            <input type="text" placeholder="What are you looking for?" class="form-control">
                        </div>
                        <span class="input-group-btn">
                            <button class="btn btn-default" type="button">Search!</button>
                        </span>
                    </div>
                </div>
            </div>
            <div id="products" class="row list-group">
            </div>
        </div>
    </body>
</html>

Как видите здесь я набросал базовую структуру и подключил Twitter Bootstrap через CDN, в реальном приложении так делать не стоит.

Подключение Vue.js и vue-resource:

Добавьте эти строки перед закрывающим тегом body :

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-resource/1.0.1/vue-resource.min.js"></script>
<script src="/js/app.js"></script>

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

Затем создайте app.js файл внутри директории public/js (удалите, если есть уже такая) с таким содержанием:

new Vue({
    el: 'body',
});

Это означает, что мы создаем инстанс Vue и привязываем ее к элементу body.

Vue.js данные и их привязка к шаблону:

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

data: {
    products: [],
    loading: false,
    error: false,
    query: ''
},

Теперь нам надо немного изменить наш HTML, вот что мы сделаем:

1) Добавим атрибут `v-model` к строке поиска для того что бы связать ее с атрибутом в объекте данных
<input type="text" placeholder="What are you looking for?" class="form-control" v-model="query">

2) Добавим отображение надписи Searching... на кнопке пока происходит поисковый запрос к серверу. Если запрос не исполняется, то надпись на кнопке будет Search!

<button class="btn btn-default" type="button" v-if="!loading">Search!</button>
<button class="btn btn-default" type="button" disabled="disabled" v-if="loading">Searching...</button>

3) Для отображения ошибки добавим в конце следующий контейнер

<div class="alert alert-danger" role="alert" v-if="error">
    <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
    @{{ error }}
</div>

Для отображения переменных Vue.js использует синтаксис {{ }}, но, т.к. Laravel использует такой же синтаксис в шаблонизаторе используем @ в начале, чтобы предотвратить рендеринг на сервере.

Получение и отображение данных:

Нам остался последний и самый важный шаг – получить данные поиска. Начнем с добавления слушателя события нажатия кнопки поиска, делается это при помощи атрибута @click:

<button class="btn btn-default" type="button" @click="search()" v-if="!loading">Search!</button>

Добавим метод search в наш объект:

methods: {
    search: function() {
        // Очистим сообщение об ошибке.
        this.error = '';
        // Опустошим набор данных.
        this.products = [];
        // Установим признак загрузки данных в true,  
        // для отображения процесса поиска "Searching...".
        this.loading = true;

        // делаем get запрос к нашему API и передаем в него поисковый запрос.
        this.$http.get('/api/search?q=' + this.query).then((response) => {
            // Елси ошибки нет, заполняем массив products, в случае ошибки заполняем ее
            response.body.error ? this.error = response.body.error : this.products = response.body;
            // Запрос завершен. Меняем статус загрузки
            this.loading = false;
            // Очищаем поисковое слово.
            this.query = '';
        });
    }
}

И наконец добавляем HTML для вывода наших товаров.

<div class="item col-xs-4 col-lg-4" v-for="product in products">
    <div class="thumbnail">
        <img class="group list-group-image" :src="product.image" alt="@{{ product.title }}" />
        <div class="caption">
            <h4 class="group inner list-group-item-heading">@{{ product.title }}</h4>
            <p class="group inner list-group-item-text">@{{ product.description }}</p>
            <div class="row">
                <div class="col-xs-12 col-md-6">
                    <p class="lead">$@{{ product.price }}</p>
                </div>
                <div class="col-xs-12 col-md-6">
                    <a class="btn btn-success" href="#">Add to cart</a>
                </div>
            </div>
        </div>
    </div>
</div>

Здесь мы сделали вывод результатов в цикле при помощи директивы v-for и отобразили данные, как и раньше при помощи синтаксиса {{}}.

У вас должно получиться примерно следущее:

Заключение:

Код к статье можно взять здесь https://github.com/RachidLaasri/laravel-scout-and-vuejs Автор будет рад ответить на ваши вопросы и предложения в комментариях к оригинальной статье

]>
2016-09-24T16:43:00+03:00
David Hemphill <![CDATA[Представители (Presenters) в Laravel]> /blog/tutorials/presenters-in-laravel В долгосрочном Laravel проекте шансы, что ваши модели разрастутся со временем очень велики. Если в течение жизни проекта функционал будет наслаиваться, эти классы могут разрастись настолько, что их трудно будет поддерживать.

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

Опасно давать моделям бесконтрольно упиваться «логическим самогоном». Конечно, они не пустят вас по миру, не запустят все ядерное оружие в мире или сдадут ваше местоположение злобным пришельцам, опасность заключается в том, что позже с ними будет трудно работать.

Иногда можно опереться на шаблон проектирования «Декоратор» (Decorator Pattern) которой позволит отрефакторить часть функционала при помощи контекстно-зависимых классов, что конечно разгрузит немного наши модели. Например, можно использовать декораторы для разделения форматирования вывода в PDFs, CSV, или API.

Что такое Декоратор (Decorator)? Что такое Представитель (Presenter)?

Декоратор – это объект, который оборачивает другой объект с целью добавления в него функционала. Он также делегирует базовому (декорируемому) объекту вызов методов, не представленных в нем самом. Декораторы полезны, когда необходимо модифицировать функционал класса без наследования. Этот паттерн можно использовать для добавления опций типа логгирования, контроля доступа, и тому подобных вещей.

Представитель – разновидность декоратора, который используется для представления объекта в том или ином виде: например в Blade шаблоне или качестве ответа API.

Форматирование коллекции пользователей для ответа API

Представим, что у нас есть некая коллекция объектов, которую надо вернуть в качестве ответа на запрос к API. В Laravel нам особо ничего делать не надо, просто вернем Collection, которая автоматически трансформируется в строку JSON. Рассмотрим пример, сделаем вызов Eloquent в контроллере, как обычно:

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;

class UsersController extends Controller
{
    public function index()
    {
        return User::all();
    }
}

Метод all возвращает Collection состоящую из всех объектов User нашей базы данных. Каждый объект User будет содержать все поля из таблицы. Пароли и другая секретная информация будет фигурировать в ответе. Затем Laravel трансформирует результат выполнения метода all в JSON строку.

Примечание: Ну конечно я знаю, что Eloquent поддерживает скрывание атрибутов в json представлении модели путем добавления их в массив $hidden . Не надо паники.

Получается, что этот метод не очень подходит для построения API из-за проблем с безопасностью, как минимум. Например, мы не должны отправлять в ответе хеши паролей, скорее всего не захотим отдавать в неформатированном виде такие проперти как created_at и updated_at. Также атрибуты типа is_active надо будет отдавать не интами а строкой типа true или false. Всего этого можно достичь, если обернуть, или «задекорировать» наши объекты другими объектами.

Встречайте шаблон проектирования «Представитель» (Presenter)

Теперь, когда у нас есть коллекция объектов User , как нам отправить их в представление в декорированной форме? Нам нужен класс, который будет действовать в качестве представителя. В данном примере класс UserPresenter может выглядеть следующим образом. Обратите внимание, что наш представитель делегирует магические вызовы атрибутов first_name, last_name, и created_at исходной модели, потому что этих атрибутов нет в самом представителе:

<?php

namespace App\Users;

class UserPresenter
{
    protected $model;

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

    public function __call($method, $args)
    {
        return call_user_func_array([$this->model, $method], $args);
    }

    public function __get($name)
    {
        return $this->model->{$name};
    }

    public function fullName()
    {
        return trim($this->model->first_name . ' ' . $this->model->last_name);
    }
}

Я люблю тупые аналогии, вот одна для данного случая: декоратор – это как костюм Бэтмена для Брюса Уэйна. А, если вы выросли на тех же игрушках, что и я, вы точно знаете, что у Бэтмена целый набор костюмов на все случаи жизни. Как и костюмы, мы можем иметь декораторы для разных ситуаций в нашем приложении.

Давайте теперь применим это знание и переименуем наш класс-представитель по что-то более подходящее по смыслу. Назовем его ApiPresenter и поместим в папку Presenters . И, пока мы здесь, давайте выделим то, что может быть пере-использовано, в базовый абстрактный класс Presenter:

<?php

namespace App\Presenter;

abstract class Presenter
{
    protected $model;

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

    public function __call($method, $args)
    {
        return call_user_func_array([$this->model, $method], $args);
    }

    public function __get($name)
    {
        return $this->model->{$name};
    }
}

Немножко прибрались и можем продолжать. Давайте придадим еще супер-сил нашей модели, добавим новый метод в ApiPresenter.

<?php

namespace App\Users\Presenters;

use App\Presenter\Presenter;

class ApiPresenter extends Presenter
{
    public function fullName()
    {
        return trim($this->model->first_name . ' ' . $this->model->last_name);
    }

    public function createdAt()
    {
        return $this->model->created_at->format('n/j/Y');
    }
}

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

Вы можете также возразить «Я оставлю created_at нетронутым и сделаю несколько мутаторов. Типа friendlyCreatedAt(), pdfCreatedAt(), и createdAtAsYear()»

Конечно вы можете, вы ведь взрослые люди и способны принимать собственные решения, но лично я не фанат такого подхода.

Конечно, мы все когда-то так делали, но огромный плюс в том, чтобы вынести подобный функционал в отдельный класс – наша модель на разрастается, не ширится ее область интересов. Мы доверяем другому классу заботиться о форматировании модели для API.

Давайте добавим еще методы в Presenter.

<?php

namespace App\Users\Presenters;

class ApiPresenter
{
    public function fullName()
    {
        return trim($this->model->full_name . ' ' . $this->model->last_name);
    }

    public function createdAt()
    {
        return $this->model->created_at->format('n/j/Y');
    }

    public function isActive()
    {
        return (bool) $this->model->is_active;
    }

    public function role()
    {
        if ($this->model->is_admin) {
            return 'Admin';
        }

        return 'User';
    }
}

Что добавилось: мы приводим атрибут is_active к булеву значению вместо tinyint. Мы также представляем роль пользователя в виде строки. Вернемся к нашему контроллеру, теперь мы можем использовать методы представителя для построения ответа:

<?php

namespace App\Http\Controllers;

use App\Users\Presenters\ApiPresenter;
use App\Http\Controllers\Controller;

class UsersController extends Controller
{
    public function show($id)
    {
        $user = new ApiPresenter(User::findOrFail($id));

        return response()->json([
            'name' => $user->fullName(),
            'role' => $user->role(),
            'created_at' => $user->createdAt(),
            'is_active' => $user->isActive(),
        ]);
    }
}

Замечательно! Теперь мы можем вернуть только релевантную информацию в API, да еще и код почистили немного. Что еще круче, если нам необходимо значение атрибута, которого нет в ApiPresenter, но которое есть в самом классе User, мы можем просто вернуть его динамически, как в случае с простой моделью:

<?php

return response()->json([
    'first_name' => $user->first_name,
    'last_name' => $user->last_name,
    'name' => $user->fullName(),
    'role' => $user->role(),
    'created_at' => $user->createdAt(),
    'is_active' => $user->isActive(),
]);

Декорируем всю коллекцию пользователей

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

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Users\Presenters\ApiPresenter;

class UsersController extends Controller
{
    public function index()
    {
        $users = User::all();

        $apiUsers = [];

        foreach ($users as $user) {
            $apiUser = new ApiPresenter($user);

            $apiUsers[] = [
                'first_name' => $apiUser->model->first_name,
                'last_name' => $apiUser->model->last_name,
                'name' => $apiUser->fullName(),
                'role' => $apiUser->role(),
                'created_at' => $apiUser->createdAt(),
                'is_active' => $apiUser->isActive(),
            ];
        }

        return response()->json($apiUsers);
    }
}

И такой вариант может вполне жить, вы можете так сделать и никто не умрет. Однако, как-то корявенько это выглядит.

Вместо этого я бы сделал решение на основе макроса коллекции, примерно так:

<?php

Collection::macro('present', function ($class) {
    return $this->map(function ($model) use ($class) {
        return new $class($model);
    });
});

Теперь, если внедрить этот макрос в сервис-провайдер, можно добавить его вызов после User::all() с необходимым представителем в качестве параметра:

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Users\Presenters\ApiPresenter;

class UsersController extends Controller
{
    public function index()
    {
        $users = User::all()
            ->present(ApiPresenter::class)
            ->map(function ($user) {
                return [
                    'first_name' => $user->first_name,
                    'last_name' => $user->last_name,
                    'name' => $user->fullName(),
                    'role' => $user->role(),
                    'created_at' => $user->createdAt(),
                    'is_active' => $user->isActive(),
                ];
            });

        return response()->json($users);
    }
}

Как по мне, все становится довольно круто. Макрос present становится звеном цепи. Каждая модель в коллекции оборачивается представителем. Затем получившуюсю коллекцию можно передать еще в один декоратор, чтобы скомбинировать ответ из нескольких обработчиков. Это как надеть несколько костюмов Бэтмена!

Итого, декораторы/представители – мощный инструмент, который надо держать под рукой. Их легко писать и легко тестировать. Используйте их при необходимости. Они помогут вам вычистить кучу кода, если применить их в нужное время, в нужном месте.

Однако, это еще не все.

Если вы думаете также, как и я, вы точно тоскуете по старым денькам, когда можно было тупо вернуть коллекцию из контроллера и получить отличную JSON строку. Нам придется добавить поддержку этого, мы также наверняка будете скучать по возможности сконвертировать коллекцию в массив, это тоже придется добавить. А что еще было круто? Если можно было просто сделать вызов present у самой модели и на этом все. И было бы нереально круто иметь отличную функцию-помощника, в которую можно было скормить объект любого типа?

Позвольте представить свой пакет Hemp/Presenter. Он делает, все что мы обсудили выше и все, что пожелали в конце. И он проверен. Попробуйте его и поделитесь своим опытом!

]>
2016-09-14T17:24:00+03:00