Локализованное форматирование даты в Laravel

Сейчас я приведу очень простой способ того, как можно сделать в проекте локализованное форматирование даты на фреймворке Laravel. В конечном итоге мы получим результат, при котором получая свойство модели, к примеру, created_at (timestamp формат: 2015-10-20 19:45:56) — преобразуется в «20 Октября в 19:45». Форматирование можно будет настроить по вкусу или исходя из нужд.

Создание файла с переводами


Создаем date.php в директории app/lang/ru (если текущий язык русский). В его содержании будет формат вывода даты со временем и правильные окончания для месяцев.

<?php

return [
    'later'             => ':date в :time',
    'today'             => 'сегодня в :time',
    'yesterday'         => 'вчера в :time',
    'month_declensions' => [
        'January'   => 'Января',
        'February'  => 'Февраля',
        'March'     => 'Марта',
        'April'     => 'Апреля',
        'May'       => 'Мая',
        'June'      => 'Июня',
        'July'      => 'Июля',
        'August'    => 'Августа',
        'September' => 'Сентября',
        'October'   => 'Октября',
        'November'  => 'Ноября',
        'December'  => 'Декабря'
    ]
];


Для английского языка:
<?php

return [
    'later'             => ':date at :time',
    'today'             => 'today at :time',
    'yesterday'         => 'yesterday at :time',
    'month_declensions' => [
        'January'   => 'January',
        'February'  => 'February',
        'March'     => 'March',
        'April'     => 'April',
        'May'       => 'May',
        'June'      => 'June',
        'July'      => 'July',
        'August'    => 'August',
        'September' => 'September',
        'October'   => 'October',
        'November'  => 'November',
        'December'  => 'December'
    ]
];



Создание библиотеки


Теперь реализуем библиотеку, которая будет выполнять всю нашу логику для преобразования даты.
Создаем папку app/libraries (если ее нету) и файл DateFormat.php в папке Date, полный путь будет — app/libraries/Date/DateFormat.php.

<?php

namespace Date;

class DateFormat
{

    /**
     * данный метод написан на коленке (нужно переписать)
     */
    public static function post($time)
    {
        $timestamp = strtotime($time);
        $published = date('d.m.Y', $timestamp);

        if ($published === date('d.m.Y')) {
            return trans('date.today', ['time' => date('H:i', $timestamp)]);
        } elseif ($published === date('d.m.Y', strtotime('-1 day'))) {
            return trans('date.yesterday', ['time' => date('H:i', $timestamp)]);
        } else {
            $formatted = trans('date.later', [
                'time' => date('H:i', $timestamp),
                'date' => date('d F' . (date('Y', $timestamp) === date('Y') ? null : ' Y'), $timestamp)
            ]);

            return strtr($formatted, trans('date.month_declensions'));
        }
    }

}


Автозагрузка библиотек


Для того, чтобы все библиотеки были доступны для использования, добавим в composer.json автозагрузку классов с нашей директории app/libraries:

{
    "autoload": {
        "classmap": [
            "app/libraries"
        ]
    }
}


И обновим кэш composer'a:

composer dump-autoload


Базовая модель


Здесь можно поступить несколькими способами:
  • Создать абстрактную базовую модель и унаследоваться от нее;
  • Создать типаж (trait) и добавлять его использование в наши модели.

Я выбрал первый способ, так как он показался более практичным.

В созданном файле app/models/Model.php добавляем метод, который будет вызываться при получении свойства created_at.
Вызываться он будет автоматически, нам делать для этого ничего не нужно.

<?php

use Date\DateFormat;
use Illuminate\Database\Eloquent\Model as Eloquent;

abstract class Model extends Eloquent
{

    public function getCreatedAtAttribute($attr)
    {
        return DateFormat::post($attr);
    }

}


Данный код говорит сам за себя: при получении свойства модели created_at (название атрибута преобразуется в camelCase), верни значение обработанное методом post с помощью нашей библиотеки DateFormat. Также, можно создать методы для атрибутов updated_at, deleted_at и прочих.

Теперь можно проверить все на практике, ниже будет пример модели и контроллера для получения всех постов.

Модель app/models/Post.php:

<?php

class Post extends Model
{

    /**
     * @var string The table associated with the model.
     */
    protected $table = 'posts';
}


Контроллер app/controllers/PostController.php
<?php

class PostController extends Controller
{

    public function index()
    {
        return Post::all();
    }

}


Обратимся к нашему контроллеру (не забудьте прописать условия маршрутизации) и получим на выходе массив наших постов в json-формате, где свойство created_at будет отформатировано в необходимом языке и формате.

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

Буду рад услышать комментарии по коду. Хотелось бы узнать, какие есть best practices для решения подобных задач.
Share post
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 16

    +2
    Интересно, мне всегда казалось, что в PHP для локализации стандартом де-факто является расширение intl, надо заметить, что оно сильно упрощает вывод дат. В Symfony, например, эта проблема решается в расширении к шаблонизатору, которое зависит от intl.
    Плюс, мне кажется, что всё-таки форматирование данных находится за пределами ответственности модели, т.е. метод getCreatedAtAttribute излишен, а код, в нем реализованный, попадает в зону ответственности вьюхи.
      +1
      И кстати, зачем суффикс ...Attribute в геттере? Разве не любое значение, возвращаемое геттером, будет являться атрибутом объекта?
        0
        Если заглянуть во внутренности Laravel, то можно увидеть, что модель Illuminate\Database\Eloquent\Model ищет метод «getНашАтрибутAttribute».
        Подробнее: github.com/laravel/framework/blob/4.2/src/Illuminate/Database/Eloquent/Model.php#L2499
          0
          А, ясно, это Ларавеловские штуки то есть. Спасибо за ссылку, теперь понятно.
            0
            Всегда пожалуйста)
        0
        К примеру, многие пишут приложение в связке с AngularJS (либо другие frontend-фреймворки), то контроллер отдает данные в json-формате.
        Если во всех контроллерах циклически обрабатывать модели, где нужно преобразовать даты — получится большое скопление дублирующегося кода.
        А в проектах на подобии блог/информационный портал/соц. сеть и т.д. — таких мест будет очень много.
          +1
          Ну, на мой взгляд это и не ответственность контроллера. В стеке должен быть выделен шаг сериализации, который на входе получает модель, а на выходе у него JSON, в котором даты трансформируются так, как вам удобно.
          Если у вас более сложный случай, когда данные модели приходят в одном представлении, а отображаться должны в другом — вы можете всегда возвращать таймштампы в едином формате, а на клиенте использовать Moment.js или аналог. Правда, в этом случае локализация тоже переедет на клиент, но на мой взгляд это скорее плюс — работать будет быстрее.
            0
            Откройте для себя jms/serializer
          0
          Сам частенько реализовывал такие вещи серверно, но сейчас практически уверен, что локализация даты — задача клиента.
          Особенно сейчас, когда есть семантичный и удобный тег <time>.
          Даже простенькую библиотечку для этого написал — https://github.com/maximal/time.js/.
          Есть ещё всякие штуки потяжелее: moment.js, например.
            0
            Не задумывался над этим ранее… Действительно, лучше формат даты делать на стороне клиента (тем более можно подхватить часовой пояс и корректировать время от его значения). Круто, нужно будет попробовать. Только нужно сделать генерацию json-файла с необходимыми переводами на различные языки.
            0
            Это как раз то, чего давно не хватает, и что давно просят добавить, в моей библиотечке localized-carbon. Сейчас она только умеет локализовывать разницу во времени, типо «3 часа назад», «5 лет назад» и т.д.
              0
              это умеет оригинальный carbon
                0
                А в 2014-ом умел?
                  0
                  Тогда было бы великолепно на github в описании к пакету написать его применимость относительно текущего положения дел в самом carbon.
              0
              Спасибо, хорошее решение, только пару нареканий по стилю.
              Для работы с датой стоит использовать имеющуюся библиотеку Carbon и не стоит вешать это на get*Attribute чтобы не сломать поведение в других местах. Это логика отображение и её нужно делать во вьюхе или навешивать через Decorator-Presenter.
                0
                Спасибо за полезный комментарий!

              Only users with full accounts can post comments. Log in, please.