Как стать автором
Обновить

Комментарии 67

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

if(isset($_GET['language'])) {
    Yii::app()->language = $_GET['language'];


Если потом во view кто-то напишет например так:
echo 'Your locale: '.Yii::app()->language

то через урл можно сделать xss. (теоретически)
Надо так:
Yii::app()->request->getParam('language');
чем вам поможет getParam?
Оу… Параметр не эскейпится. Ушел гуглить.
Мне кажется если будет ХСС то это вопрос к тому, кто писал view. Заранее ескейпить не стоит. т.к данные до того момента когда они нужны должны оставаться as is.
Я придерживаюсь мнения что лучше 2 раза защитится чем схлопотать потом. Тем более что если параметр language будет содержать что-то кроме 2 латинских букв (в крайнем случае больше двух) то понятно что он не валидный и что-то пошло не так. А «лишняя» информация тут потребоваться не может.
Если два раза защититься, то вылезут символы экранирования пользователю. Так что нифига не лучше 2 раза защититься. Лучше 1 раз защититься нормально.
Ну а проверить lang на 2 латинских буквы — да, вполне можно.
Этот пример скорее исключение. Конечно если мы 2 раза экранируем символы ничего хорошего не будет. Но если мы 2 раза проверим id товара на integer или если 2 раза проверим язык на допустимый, или 2 раза из введенного текста вырежем зараженные фрагменты ничего плохого не будет. Я не говорю что нужно делать все 2 раза. Понятно что если мы 100% уверены что этот фрагмент кода получит безопасный контент проверять что либо бессмысленно. Но если есть сомнения, я лучше второй раз сделаю проверку.
Ну, имхо, два раза делать что-то впринципе — глупо. Надо чётко знать, что и где необходимо экранировать, иначе прям антипаттерн получается — программирование наугад. «А давайте ещё тут поставим проверку, на всякий случай».
Я согласен, что необходимо сделать валидацию в данном конкретном случае, но надо не просто проверить на «две латинских буквы», а проверить, есть ли такой язык и если нету, то переадресовать пользователя на язык-по-умолчанию. Ведь пользователь может увидеть, скажем где-то в адресе: eng.example.com и заменить его на rus.example.com, которого в системе может и не быть.
Просто в данном случае валидацию языка надо делать ДО каких-либо других действий, связанных с выбранным языком. Как минимум надо проверить поддерживает ли сайт вообще указанный пользователем китайский язык или нет. А валидированный код языка эскейпить уже ни к чему. Ну, вобщем, это скорее не к вам коммент, а к таралу.
Далеко не всегда четко знаешь какие данные 100% валидные а какие стоит фильтровать. Тем более что бывают ситуации когда все указывает что тут могут быть только безопасные данные, а на самом деле все может быть по другому. В больших проектах эта ситуация еще больше заметна. Хорошие пример к сожалению в голову сейчас не приходит.
У меня обычно каждый объект который принимает данные из вне делает хоть и минимальную но проверку (конечно не всех данных, а скорее ключевых для него).
MVC же! О чем вы вообще говорите?
И это что означает что у меня не может быть объектов которые принимают данные? Вы хотите сказать что в MVC кроме как представления, контроллера и модели ничего нету?
Ещё может быть (судя по реализациям — должен быть) фронт-контроллер :)
Если у меня в рамках серверного MVC должен вводиться JS-код, исполняемый со стороны клиента? тупая экранизация всего подряд здесь не поможет.
А тут уже понадобились для присваивания Yii::app()->language со вполне определенной семантикой и фиксированным набором допустимых значений.
echo CHtml::encode($str) спасет отца демократии.
Ну впринципе можно было просто выбранный язык просмотреть существует ли он в локали, или взять из запроса. Я думаю можно было сделать и попроще, щас заминусуют правда :) Вся задача то сводится: поправить urlManager и проверить наличие языка в системе, а тут нагородили)
Ой. Теперь понял. Проверка происходит в роутах.
<language:(ru|ua|en)>
В общеобразовательных целях спрашиваю — сильно ли плохо — не иметь параметра «язык» в uri? (язык хранится в сессии\кукисах).
Поисковая машина будет индексировать ваш сайт всегда на одном языке. Ни один из поисковиков не передает выданные ему ранее cookie.
Человек обновил страницу — текст исчез, появился другой.
Поисковик не нашёл одну из версий. Заказчик теряет клиентов на одном из языков. Выясняется не сразу.,
Два разных бота одного поисковика решили, что их обманывают и по одному URL два разных текста ботам и людям выдают. Сайт выкидывают из индекса.
Человек добавил страницу в закладки, вышел, зашёл — там не понятно.
Человек кинул ссылку другу, а там другу ничего не понятно.
Делаем суперпроизводительный сайт в кластере, отказались от сессий, сайт сломался.

Сильно ли плохо — решайте сами. Моё мнение — ужасно.
переименуйте ссылки-примеры, сейчас сайт по феншую переживает адскую нагрузку :-)
173 посетителя фигня.
А почему вы используете «ua» для обозначения украинского языка вместо «uk»?
uk больше united kingdom
uk — Великобритания, en — английский язык
ua — Украина, uk — украинский язык
очевидно же, что пользователи будут путаться. а вообще есть нормальное обозначение локалей вида UK_en, US_en, RU_ru и т.д. )
конечно пользователи будут путаться если смешивать коды разных сущностей (стран и языков) в одном наборе.
еще можно использовать 3-х символьные коды языков (rus, eng, ukr и т.п.)
Не относительно темы, но все же: меня в Yii всегда смущала любовь к статическим методам и классам. Взять тот же CHtml. Захочу я переделать метод для текстового поля, чтобы он мне, допустим, дефолтный класс приписывал, так придется или в исходник лезть, либо по всему проекту менять вызов с CHtml на мой собственный наследник. Если была бы возможность получать объект из реестра или через $this->chml во вьюхе, было бы в разы проще.

Опять же, в кажом проекте нужно повторять одну и ту же вещь — создавать собственный базовый контроллер и наследовать свои контроллеры уже от него, чтобы в очередной раз не изменять все контроллеры когда придется вычленить общий функционал в базовый контроллер.
Например, мне достаточно CHtml, а вам нет.
Если во фреймворке заранее будет излишняя слоистость, то я от неё не смогу избавиться, а если наоборот, то всё ок, т.к. вы всегда сможете добавить ещё один слой.
Или тормоза у всех, или только у тех, кому это надо для реализации задачи.
в том и дело, что в, например, Zend Framework можно на более позднем уровне заменить класс хелпера своим без необходимости править 90% вьюшек. В случае с Chtml вы не сможете добавить еще один слой в рабочий проект. Была бы возможность в PHP динамически подменять методы в классе, все ок было бы, чем собственно и любит заниматься Rails — идейный вдохновитель Yii.

Опять же, торомоза от того, что вместо статического класса используется объект, который инициализируется в бутстрапе — звучит странно.
Для переопределения методов можно использовать runkit_method_redefine. Правда надо добавить runkit в php для этого.
Интересно чем же это обычная практика использования ООП может быть такой назойливой. Не хотите для каждого проекта создавать свой базовый контроллер — не создавайте. Это не значит что разработчики заранее должны предугадать все Ваши хотелки.
Это, кстати, совершенно не обычная практика ооп. Архитектура на самом деле хромает.
С другой стороны на практике оно реально мало когда нужно.
Сравнить с теми же Rails, в которых можно без проблем делать monkey patching или дописывать в существующий класс, даже там есть базовый ApplicationController, в котором описываются всякие вызовы к acl, auth и прочие общие вызовы. Просто это реально удобней, и я не припомню проектов, где не приходилось бы создавать собственный контроллер, за исключением каких-то визиток.

Поймите, мне нравится Yii. По сравнению с ололо-энтерпрайз-спринг-на-пхп Симфони 2 или давайте-будем-юзать-как-можно-больше-паттерном ЗФ Yii просто няшка, но на проект побольше пришлось взять ЗФ, потому что в нем можно вносить изменения по необходимости, а не предугадывать их в начале разработки. К примеру, возникла необходимость добавить логирование переводимых фраз. В зф это решается подключением собственного адаптера при инициализации приложения. В Yii нужно было либо заменять все вызовы Yii::t, либо хачить фреймворк (может сейчас уже другая архитектура, я говорю за два года назад). Аналогично и со всеми вызовами статики.

Я считаю, что Yii стоит подтянуть менеджмент зависимостей в коде если он хочет добиться того же положения, что и Rails в своей среде. Понятно, что из-за особенностей пхп придется кое где пожертвовать парой символов, но жизнь это облегчит основательно.
Не могу сказать как было раньше, но сейчас думаю задачу с логгированием переводимых фраз точно так же можно решить конфигурированием компонента приложения MessageSource.
«По сравнению с ололо-энтерпрайз-спринг-на-пхп Симфони 2 или давайте-будем-юзать-как-можно-больше-паттерном ЗФ Yii просто няшка»

очень мило )
На счет базового контроллера согласен, могли бы его в стандартном webapp генерировать.
Этом случае вам помогут поведения.
Прошу прощения за бред выше. Вообще не о том подумал.
Вроде ж, в новой версии Yii можно подменять фреймворковые классы своими. Всякие CHtml и прочие. Ни одна IDE, конечно, не распознает этого финта, поэтому автодополнения кода не дождаться, но на уровне фреймворка — можно, работать будет. Хотя сам пока не пробовал.
Почему в контроллере используется __construct а не init?
Или еще лучше вынести в поведения (behaviors), что бы не засорять контроллер, например:

class LanguageBehavior extends CBehavior
{

    public function attach($owner)
    {
        $owner->attachEventHandler('onBeginRequest', array($this, 'handleLanguageBehavior'));
    }

    public function handleLanguageBehavior($event)
    {
        $app  = Yii::app();
        $user = $app->user;

        if (isset($_GET['_lang']))
        {
            $app->language = $_GET['_lang'];
            $user->setState('_lang', $_GET['_lang']);
            $cookie = new CHttpCookie('_lang', $_GET['_lang']);
            $cookie->expire = time() + (60 * 60 * 24 * 365); // (1 year)
            $app->request->cookies['_lang'] = $cookie;
            /*
            * другой код, например обновление кеша некоторых компонентов, которые изменяются при смене языка
            */
        }
        else if ($user->hasState('_lang'))
            $app->language = $user->getState('_lang');
        else if (isset($app->request->cookies['_lang']))
            $app->language = $app->request->cookies['_lang']->value;
    }

}


Ну и в конфиге:

    'behaviors' => array(
        ...
        'onBeginRequest' => array(
            'class'  => 'application.components.behaviors.LanguageBehavior'
        ),
    ),


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

только мне это режет глаз? :)
Спасибо что обратили внимание, подправил.
Почему все поголовно забывают, что дефолтный язык передается браузером в хедерах? Не забывайте про это!
А поисковики передают? Не получится так, что у гугла и яндекса разные главные страницы?
Смотрите, у вас есть следующие источники языка в порядке приоритета:

1. Идентификатор в URL.
2. Данные в сессии/кукисе (но зачем? но пусть будет, например).
3. Хедер в браузере.
4. Некий дефолт самого приложения.

Если вы не находите идентификатор в каком-то из источников, вы топаете на уровень ниже и делаете редирект 302 на страницу с правильным URL-ом (иначе поисковики сломаются). Таким образом вас вообще не волнует что шлют поисковые пауки — у вас всегда есть свой дефолт. К тому же вы на каждом этапе должны проверять поддерживает ли указанный язык сайт или нет, только дефолт не проверяется. Ну и всё, problem solved!
mysupersite.ru/ru/contacts для русского языка
mysupersite.ru/en/contacts для английского языка

Не лучше ли было бы так?
mysupersite.ru/ru/kontakty / mysupersite.ru/ru/контакты
mysupersite.ru/en/contacts

Плюс можно было UrlManager сделать так, чтобы «language:(ru|ua|en)» в каждом роуте не писать — всё-таки копипаста. Добавление/удаление языка выливается в найти/заменить. Пока языков мало, выглядит сносно, а как быть с тридцатью языками?
Для пользователя абсолютно всё-равно, что написано в урле. Но это сикрет, никому не говорите!
В целом да. Чую, скоро урлы вообще скроют из интерфейса браузеров и не дадут включать. :) Но поисковикам пока не совсем пофиг, да и когда в аське кидаешь ссылку — текст полезен.
Для поисковика урлы разные, если в них хотя бы один разный символ. То есть /ru/contacts и /en/contacts — разные URL-ы. А для программиста и контент-менеджера писать для каждого языка свой брэдкрамб — это пипец. Так что вот, не придумывайте сущности без их необходимости. KISS, так сказать.
Заказчик может захотеть индивидуальные урлы для каждого языка. Если страниц и языков относительно немного, то вполне себе адекватное желание. Если перевод страницы контактов одолели, то перевести хлебную крошку для неё — это сразу «пипец»? :)

Я-то сам предпочитаю иметь единые английские урлы, но не отвечать же заказчикам из-за этого: «Не сделаю, ты хочешь странного».
Если заказчик попросит, то мы сделаем translates на поле и всё, problem solved. Ну я хз как это будет в PHP, но в рельсах вот так, одной строчкой.
Точно знаю, что поисковики обрабатывают транслит и кириллицу в урлах на предмет релевантности запросу. А вот переведут они запрос с русского на английский — под сомнением.
Хаха, а ещё надо мета теги заполнять, да?
Над урлами я лично проводил эксперименты. /kontakty вс /contacts показали преимущество в 100% запросов по гуглу и яндексу, что я придумал со словом «контакты», включая такие популярные как «рога и копыта контакты».

Никаких внешних ссылок, никакого склеивания, одинаковые (в рамках эксперимента) пинги. Даже давал фору — contacts добавлял на несколько секунд раньше. Не помогла фора.
А чем LangUrlManager не устроил?
А как будет работать с модулями?
А что именно смущает?
Полагаю что для модулей нужно прописывать отдельные роутинги.
В целом да. Параметры в URL передаются, и для верной отработки надо будет в модуль добавить что-то типа

class AdminModule extends CWebModule {
   public function init() {
        Yii::app()->getUrlManager()->addRules(
          array(
              '<language:(ru|ua|en)>/admin' => 'admin',
              '<language:(ru|ua|en)>/admin/<controller:\w+>' => 'admin/<controller>',
              '<language:(ru|ua|en)>/admin/<controller:\w+>/<action:\w+>' => 'admin/<controller>/<action>',
         ));
...

(код не проверял, но думаю что должно работать)
В общем случае достаточно в конфиге приложения добавить:
		'urlManager'=>array(
			...
			'rules'=>array(
				...
				'<language:(ru|ua|en)>/<module:\w+>/<controller:\w+>/<action:\w+>/<id:\d+>' => '<module>/<controller>/<action>/<id>',
				'<language:(ru|ua|en)>/<module:\w+>/<controller:\w+>/<action:\w+>' => '<module>/<controller>/<action>',
				...
			),
		),
да, так и сделал )
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации