Решение проблемы с кодировкой данных из MySQL в Symfony

в 9:55, , рубрики: CP1251, doctrine, Doctrine ORM, mysql, symfony, twig, utf8, метки: , , , , ,

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

Предыстория:

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

Twig_Error_Runtime: An exception has been thrown during the rendering of a template
(«Warning: htmlspecialchars() [function.htmlspecialchars]: Invalid multibyte sequence in argument in
/.../app/cache/prod/classes.php line ...») in "..." at line ...


Причина этой ошибки кроется в том, что Twig по умолчанию экранирует всякий вывод, в том числе и с помощью функции htmlspecialchars(), которая в данном случае спотыкается о некую Invalid multibyte sequence. И решив, что было бы неплохо посмотреть эту самую Invalid multibyte sequence, я пропустил в шаблоне вывод соответствующих переменных через фильтр raw, примерно вот так:
{{ sometext|raw }}
Выяснилось, что кириллические текстовые данные из базы почему-то приходят в кодировке cp1251, хотя кодировка импортированной базы, таблиц и соотв. полей, из которых взяты значения была utf8. В phpMyAdmin, который был мне предоставлен хостером, на вкладке «Variables» моё внимание привлекли следующие значения параметров конфигурации mysql сервера:

init connect      SET NAMES cp1251
collation database      cp1251_general_ci
collation      servercp1251_general_ci
...

Проблема:

Очевидно, что причина была именно в init connect, который при соединении выполнял SET NAMES cp1251, потому все значения передавались в приложение как cp1251.

SET NAMES

Напомню, что SET NAMES определяет кодировку в которой клиент отправляет данные на сервер а также кодировку, в которой сервер отправляет обратный ответ клиенту.

Подобного рода проблемы обычно решаются довольно просто — сразу после коннекта к базе в своём приложении выполняем запрос или группу запросов типа:
SET CHARACTER SET UTF8;
SET NAMES UTF8;

Но, как по мне, всякий лишний запрос к базе — это вообще не комильфо, да и «костыли» в коде фреймворка тоже. Поэтому поначалу я попытался решить вопрос через техподдержку.
Ребята из техподдержки хост-компании рассказали мне, что перенести базу на сервер, сконфигурированный под utf8 они не могут, т.к. все сервера у них работают с одинаковой конфигурацией на cp1251, а менять настройки сервера из-за одного проекта никто не будет, т.к.
изменения коснуться всех баз на этом сервере (я их прекрасно понимаю). Но проблему нужно было как-то решать…

Решение:

Однако, решение на Symfony оказалось довольно изящным, что и послужило поводом к написанию данной статьи.
На помощь мне пришёл крохотный EventListener, который я повесил на событие postConnect.

Для его создания нужно внести в файле app/config/config.yml (если используется YAML) в разделе «services» запись вида:

services:
    onconnect.listener:
        class: DevSomeBundleEventListenerOnConnect
        tags:
             - { name: doctrine.event_listener, event: postConnect }

Теперь в нашем Bundle, нужно создать файл listener'a, который соответствует заданному в конфигурации выше namespace (DevSomeBundleEventListenerOnConnect).
т.е. Ложим в папку Dev/SomeBundle/EventListener файл OnConnect.php следующего содержания:

<?php
//file src/Dev/SomeBundle/EventListener/OnConnect.php

namespace DevSomeBundleEventListener;
use DoctrineCommonPersistenceEventLifecycleEventArgs;
use DoctrineORMEntityManager;

class OnConnect
{
	public function postConnect( $event )
	{
		$conn = $event->getConnection();
		$conn->executeQuery("SET NAMES UTF8");
	}
}

В моём случае проблема с кодровкой была полностью устранена, и к тому же, мне не пришлось вставлять какие-либо «хаки» в doctrine или код фреймворка. Механизм прослушивания событий широко применяется в Symfony для решения самых различных задач. В описанном выше примере использовался Symfony 2.2.0 (Standard Edition).

Выводы:

  1. Обращайте внимание на пареметры настройки MySQL сервера, — некоторые сервера по умолчанию могут работать с другой кодировкой.
  2. Если у вас нет возможности настроить MySQL сервер должным образом, можно воспользоваться приёмом, описанным выше.

Автор: dimmask

Источник

Поделиться

  1. Vladimir:

    Такая же проблема была при размещении на хостинге. Спасибо за предоставленное решение!

  2. Vladimir:

    Только записи нэймспейсов надо было при помощи «/» разделять, и название класса в config.yml тоже. Видимо, от версии PHP и Symfony зависит.

* - обязательные к заполнению поля