Web Analytics Made Easy - StatCounter
Главная Блог Проблема с полями даты и времени Laravel и MSSQL

Проблема с полями даты и времени Laravel и MSSQL

Проблема с полями даты и времени Laravel и MSSQL

Формат поля datetime для базы данных mssql работает немного по другому чем такой же, для mysql и выяснить это пришлось не простым путем.

Стандартное laravel приложение использует для полей created_at и updated_at поля типа datetime.

В локальной разработке это не вызвало никаких вопросов и проблем, как и при тестировании staging окружения в AWS, однако попытки выкатить приложение на production столкнулось с проблемой.

База упорно выдавала ошибку неправильного формата datetime, утверждая что данные в нем отформатированы неверно.

The conversion of a varchar data type to a datetime data type resulted in an out-of-range value. 

Что выглядело примерно так:

Проблема с полями даты и времени Laravel и MSSQL

Путем расхода нервной системы было выясненно, что в MSSQL поле типа datetime не обладает достаточной точностью, связанной с хранением миллисекунд. Однако длительное гугление показало что в этой базе есть формат datetime2, который и используется для хранения данных datetime включающих миллисекунды.

Базовый класс Model в laravel содержит метод getDateFormat который и отвечает за формат дат, используемых для полей типа datetime и datetime2.

 <?php abstract class Model implements ArrayAccess, Arrayable, Jsonable, JsonSerializable, QueueableEntity, UrlRoutable { use Concerns\HasAttributes, Concerns\HasEvents, Concerns\HasGlobalScopes, Concerns\HasRelationships, Concerns\HasTimestamps, Concerns\HidesAttributes, Concerns\GuardsAttributes; } trait HasAttributes { /** * Get the format for database stored dates. * * @return string */ protected function getDateFormat() { return $this->dateFormat ?: $this->getConnection()->getQueryGrammar()->getDateFormat(); } } 

Кроме того все мы знаем о мутаторах в Laravel.

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

А что бы не писать их в каждую-каждую модель, я просто написал trait в котором и описал все нужные методы, подключив этот класс к каждой модели.

А так же переопределив метод getDateFormat, задав формат, который принимает production база.

<?php namespace App\Helpers\Traits; use Carbon\Carbon; trait DateTimeMutatorsTrait { protected function getDateFormat() { return config('app.datetime_format'); } protected function getCreatedAtAttribute($value) { return Carbon::createFromFormat(config('app.datetime_format'), $value)->timezone(config('app.timezone')); } protected function getUpdatedAtAttribute($value) { return Carbon::createFromFormat(config('app.datetime_format'), $value)->timezone(config('app.timezone')); } protected function getDeletedAtAttribute($value) { return Carbon::createFromFormat(config('app.datetime_format'), $value)->timezone(config('app.timezone')); } protected function setCreatedAtAttribute($value) { $this->attributes['created_at'] = Carbon::createFromFormat(config('app.datetime_format'), $value)->timezone(config('app.timezone'))->__toString(); } protected function setUpdatedAtAttribute($value) { $this->attributes['updated_at'] = Carbon::createFromFormat(config('app.datetime_format'), $value)->timezone(config('app.timezone'))->__toString(); } protected function setDeletedAtAttribute($value) { $this->attributes['deleted_at'] = Carbon::createFromFormat(config('app.datetime_format'), $value)->timezone(config('app.timezone'))->__toString(); } } 

Само значение я у меня определенно в файле config/app.php в виде:

'datetime_format' => env('DATETIME_FORMAT','Y-m-d H:i:s'), 

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

И для production сервера это значение выглядит так:

DATETIME_FORMAT="Y-m-d H:i:s.000" 

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

Этот тип ставится только если для поля, в миграции типа timestamp задан второй параметр int $precision.

А значит вам придется внести измения во все свои миграции на эту базу, что бы они выглядели примерно так:

 <?php use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateUsersTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('users', function (Blueprint $table) { $table->increments('id'); $table->string('name'); $table->string('email')->unique(); $table->string('password'); $table->rememberToken(); $table->timestamps(3); $table->timestamp('deleted_at', 3)->nullable(); $table->string('phone')->nullable(); $table->boolean('isBlocked')->default(false); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('users'); } } 

источник Laravel and MSSQL datetime field issue
автор Дмитрий Бойко