Laravel4: Валидация и кастомные название полей в сообщениях об ошибках

Есть ли у вас такая проблема что.. Нет, правильнее будет сразу скриншот показать.



Маркером отмечены сообщения о том, что валидатору не нравятся данные которые вы пытаетесь сохранить через модель. Все хорошо и здорово кроме названия поля в сообщениях. Смышленый пользователь конечно способен догадаться что за поле имеется ввиду, но очевидно чем больше полей в исходной форме, тем сложнее придется пользователю. Да и как-то.. в общем перфекционисты от этого страдают. Есть "способ вылечить" это (на самом деле документированная возможность). Выглядит это примерно так.
public function isValid()
{
  $validator = Validator::make(
    $this->toArray(),
    [
      'name' => 'required',
      'full_link' => 'required|url|unique:table_links,full_link,' . $this->id,
    ]
  );

  $validator->setAttributeNames([
    'name' => 'Название ссылки',
    'full_link' => 'Ссылка для подсчета кликов',
  ]);

  if ($validator->fails()) {
    $this->errors = $validator->errors();
  }

  return $validator->passes();
}

Теперь сообщения будут выглядеть вот так


Стало куда веселее. Осталось почитать оф. документацию по валидации.
Метод setAttributeNames можно поискать в API, он там есть, но описание скромненькое конечно.
Что касается метода isValid, то это метод модели который используется для валидации вот так
public static function boot()
{
  parent::boot();

  // before update and create
  MyModel::creating(function($item) {
    if (!$item->isValid()) return false;
  });

  MyModel::saving(function ($item) {
    if (!$item->isValid()) return false;
  });
}
То есть на модель вешаются хуки, как видно на события сохранения и создания объекта, которые собственно и вызывают наш метод валидации при соответствующих обстоятельствах.
Пример использования такой модели в контроллере выглядит примерно так
public function itemSave()
{
  $item = MyModel::find(Input::get('id'));
  if (!$item) {
    App::abort(404);
  }

  $item->name = Input::get('name');
  $item->full_link = Input::get('full_link');

  if (!$item->save()) {
    return Redirect::route('item-edit', [$item->id])->withErrors($item->errors);
  }

  return Redirect::route('item-edit', [$item->id])->withItemSaved(1);
}
Пример я упростил, но суть та же. Если сохранение отработало с ошибкой, возвращаемся назад с ошибками из валидатора, иначе всё тип-топ, сообщаем об успешном сохранении. Это всё.

P.S.: Для поддержки языковых версий можно вписывать названия свойств вот так
$validator->setAttributeNames([
  'name' => Lang::get('error.name'),
  'full_link' => Lang::get('error.full_link'),
]);

Laravel 4: with magick

Исходник статьи принадлежит гражданину @zwacky. А тем кто ещё не шарит на medium в поисках интересного - рекомендую попробовать. Темы совершенно любые, отдельной строкой отрадно, что разработка на чем угодно - в том числе.

Заметка маленькая, но приятная. Есть в Laravel такая замечательная "штука" как with. Пример классического использования можно встретить при передаче данных в шаблон или при редиректе
// template
return View::make('template.blade.php')->with('say', 'yahoo');

// redirect
return Redirect::to('user/login')->with('msg', 'wow, u failed');
Здесь все понятно, просто передаем ключ и значение. Так в чем же собственно "with magick"?
Пояснить словами мне трудно, поясню примером ниже.

Внимательный читатель документации (которым я тоже не являюсь к сожалению) нашел бы в ней более ловкий вариант передачи данных в шаблон или редирект самостоятельно. Возможно эта фишка несколько усложняет понимание кода, но мне кажется привыкаешь к этому моментально и пересаживаться обратно уже не хочется.
// template
return View::make('template.blade.php')->withSay('yahoo');
// в шаблоне будет доступно как {{ $say }}

// redirect
return Redirect::route('some-cool-route')->withAuthError('woww, auth error dude');
// в шаблоне будет доступно как {{ Session::get('auth_error') }}
То есть во все методы начинающиеся с with обрабатываются как магические. Кроме того в их названиях обрабатывается CamelCase заменяя смену регистра на нижнее подчеркивание (что демонстрирует второй пример). Таким образом на отбивании кавычек и запятых можно немного сэкономить. Вот мелочь же, а как приятно.

Да. В оф. документации можно увидеть описанное здесь поведение вот тут в параграфе "Passing Data To Views". Возможно такое поведение with встретится где-то ещё. Практически тоже самое есть в работе с Eloquent (см. статью на medium).

Laravel 4: Localization (пример решения задачи многоязыковой поддержки)

Само решение принадлежит Barry vd. Heuvel и приведено им на форуме forumsarchive.laravel.io, я только немного дорихтовал детали, довольно неуклюже пока как мне кажется, но работает.

Для понимания кода примера нужно ознакомиться с документацией по Localization (надо прочитать всё, там не много) и Routing (prefixing).
Приведенный ниже код работает так
  • Проверяет первый сегмент uri запроса (http://hostname/ru/one/two/?p=1)на соответствие какому-либо из установленных языков
  • Если первый сегмент uri соответствует какому-либо установленному языку, то устанавливается кука с этим языком на месяц, чтобы в дальнейшем пользователь мог зайти на страницу набрав только имя хоста и тут же перейти на свою языковую версию
  • В случае если первый сегмент не соответствует ниодному из установленных языков ту же проверку пробуем провести с кукой, содержащей выбранный язык
  • Далее добавляем к запрошенной ссылке текущую локаль и насильно редиректим на "правильную" ссылку. Причем локаль берется из текущего состояния приложения, то есть если ничего ни в uri ни в cookie небыло найдено, то локаль будет установлена та, что указана в конфиге приложения app/config/app.php
// set language prefix
$locale = Request::segment(1);
$langs = ['ru', 'en'];
if (in_array($locale, $langs) !== false) {

  \App::setLocale($locale);
  Cookie::queue('language', $locale, 60 * 24 * 30);
} else {

  // if no url param - try to get lang from cookie
  $locale = Cookie::get('language');
  if (in_array($locale, $langs) !== false) {
    \App::setLocale($locale);
  }

  if (!\App::runningInConsole()) {
    // save all uri & force redirect to "correct" link
    $qp = Input::query();
    $qp = (!empty($qp)) ? '?' . http_build_query($qp, null, '&') : '';
    Redirect::to('/' . Lang::getLocale() . '/' . Request::path() . $qp, 301)->send();
    exit();
  }
}

// front part routing
Route::group(['prefix' => $locale], function () {
  Route::get('/', ['as'   => 'main-page', 'uses' => function () {
    return View::make('main');
  }]);

  Route::get('/yey', ['as' => 's-yey', 'uses' => 'HomeController@sayYey']);
  Route::get('/wow', ['as' => 's-wow', 'uses' => 'HomeController@sayWow']);

}); // front part with language prefix
Я уверен что это не самое лучшее решение но оно работает. Далее, интересный вопрос с кнопкой переключения языков, то есть с той самой переключалкой из ссылок вида Ru | En которая собственно переключает текущий язык. Думаю логично хотеть чтобы при таком переключении в текущем урле ничего кроме языкового сегмента не менялось. Предлагаю свой велосипед, основанный на мане по Localization
Создадим языковые файлы для этой цели, под английский и русский языки из примера
// app/lang/en/lang_toggle.php

$segsEn = $segsRu = Request::segments();
$segsRu[0] = 'ru';
$qp = Input::query();
$qp = (!empty($qp)) ? '?' . http_build_query($qp, null, '&') : '';

return [
  'en' => [
    'text' => 'En',
    'href' => '/' . implode('/', $segsEn) . $qp,
  ],
  'ru' => [
    'text' => 'Ru',
    'href' => '/' . implode('/', $segsRu) . $qp
  ],
];
И ещё один
// app/lang/ru/lang_toggle.php

$segsEn = $segsRu = Request::segments();
$segsEn[0] = 'en';
$qp = Input::query();
$qp = (!empty($qp)) ? '?' . http_build_query($qp, null, '&') : '';

return [
  'en' => [
    'text' => 'En',
    'href' => '/' . implode('/', $segsEn) . $qp,
  ],
  'ru' => [
    'text' => 'Ru',
    'href' => '/' . implode('/', $segsRu) . $qp
  ],
];
Практически это копипаста которую я пока не придумал как залечить. Теперь у нас есть роутинг с языковым сегментом и языковые варианты контрола переключения с сохранением всех параметров uri. Осталось сделать шаблон для отображения этого контролла, вот он
{{-- app/views/lang-toggle.blade.php --}}
<div style="float:right; padding:5px;">
  <a href="@lang('lang_toggle.en.href')">@lang('lang_toggle.en.text')</a>
  |
  <a href="@lang('lang_toggle.ru.href')">@lang('lang_toggle.ru.text')</a>
</div>
Теперь осталось только подключить этот контрол где-то в лэйауте
{{-- app/views/layout-main.blade.php --}}
...
@include('lang-toggle')
...
На этом все. Повторюсь что решение наверняка не идеальное, но в масштабах вселенной не должно создавать невероятную нагрузку. В качестве упражнения, можно попробовать отпилить языковой префикс для языка по умолчанию (настройка в app/config/app.php). То есть чтобы для русского по умолчанию роутинг нормально отрабатывал без языкового сегмента в ссылке, редирект по куке при этом должен остаться рабочим.
Если кто-то раздобудет решение красивее - буду благодарен за ссылку. Особо интересует момент генерации ссылок для контрола переключения языков.