1 марта 2013 в 14:46

Вышел Symfony 2.2

Сегодня вышел Symfony 2.2.

Список нововведений из официального блога:

  • Console: Автозавершение в командной строке;
  • Console: Прогресс-бар для длинных заданий;
  • Console: Скрытие паролей в режиме командной строки;
  • Console: Предложение пользователю выбрать из списка возможных вариантов;
  • Finder: Фильтрация по пути;
  • Finder: Поддержка синтаксиса glob в методе in();
  • Finder: Увеличение скорости на некоторых платформах;
  • HttpKernel: Новый под-фреймворк для управления фрагментами ресурсов;
  • HttpKernel: Улучшен вид вывода фатальных ошибок;
  • HttpKernel: Логирование устаревших вызовов;
  • Process: Получение промежуточных результатов выполняющихся процессов;
  • Process: Перезапуск процесса;
  • Process: Получение статуса исполняющегося процесса;
  • Routing: Поддержка URL-хостов при маршрутизации;
  • Routing: Относительные URL для схемы и пути;
  • Security: Интересные утилиты безопасности;
  • Validators: Валидаторы относящиеся к платежным системам;
  • FrameworkBundle: Улучшение производительности для функциональных тестов;
  • FrameworkBundle: Кэширование статических страниц.

Также в Symfony 2.2 из существующего кода были выделены два компонента:
  • PropertyAccess;
  • Stopwatch.

Далее — более подробно о некоторых изменениях.

Компоненты


PropertyAccess

PropertyAccess предоставляет доступ к полям структур (массивов, объектов), в том числе и иерархических. Код компонента был извлечен из компонента Form в отдельный компонент, т.к. оказался весьма удобным самостоятельным инструментом, который можно использовать как в Symfony, так и отдельно от него.

Метод getValue($objectOrArray, $propertyPath) получает значение из массива или объекта в соответствии с $propertyPath:
use Symfony\Component\PropertyAccess\PropertyAccess;

// ...

$row = array();
$accessor = PropertyAccess::getPropertyAccessor();

// $row[] = $item[firstName];
$row[] = $accessor->getValue($item, '[firstName]');

// $row[] = $item->getFirstName()
$row[] = $accessor->getValue($item, 'firstName');

// $row[] = $item[user][firstName];
$row[] = $accessor->getValue($item, '[user][firstName]');

// $row[] = $item->getUser()->getFirstName()
$row[] = $accessor->getValue($item, 'user.firstName');

При обращении к объектам метод получает значение первым существующим из перечисленных способов:
$item->getProp();
$item->isProp();
$item->hasProp();
$item->__get('prop');
$item->prop;

Существует и setValue($objectOrArray, $propertyPath, $value), который устанавливает значение в $propertyPath.

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

Stopwatch

Секундомер (хотя, скорее, микросекундомер) может быть весьма полезным при отладке. Был извлечен из HttpKernel, и теперь доступен через сервис-контейнер в режиме разработки.

Так его можно использовать в контроллере:
if ($this->has('debug.stopwatch')) {
    $stopwatch = $this->get('debug.stopwatch');
}
$stopwatch->start('foo');
// ...
$stopwatch->lap('foo');
// ...
$stopwatch->lap('foo');
// ...
$event = $stopwatch->stop('foo');

Замеренные промежутки отобразятся в профайлере Symfony.

Изменения в компонентах


Поиск файлов (Finder)

// До версии 2.2 можно было искать файлы по шаблону glob или regexp
Finder::create()->files()->name('*.php');
Finder::create()->files()->name('/\.php$/');
// Теперь это доступно и для директорий
Finder::create()->path('some/special/dir');
Finder::create()->path('/^foo\/bar/');
// Синтаксис glob доступен в методе in()
Finder::create()->files()->in('src/Symfony/*/*/Resources/translations');

Повысилась и производительность Finder, т.к. критерии теперь преобразуются в нативные команды Linux, BSD и MacOS.

Маршрутизация (Routing)

Генерация URL

До версии 2.2 была возможность генерировать два вида URL.

Абсолютный URL: http://example.org/blog/what-a-wonderful-world
// Twig
{{ url('blog', { post: 'what-a-wonderful-world' }) }}

// PHP
$generator->generate('blog', array('post' => 'what-a-wonderful-world'), true);
$generator->generate('blog', array('post' => 'what-a-wonderful-world'), UrlGeneratorInterface::ABSOLUTE_URL);

URL относительно домена (по умолчанию): /blog/what-a-wonderful-world
// Twig
{{ path('blog', { post: 'what-a-wonderful-world' }) }}

// PHP
$generator->generate('blog', array('post' => 'what-a-wonderful-world'));
$generator->generate('blog', array('post' => 'what-a-wonderful-world'), UrlGeneratorInterface::ABSOLUTE_PATH);

Теперь можно генерировать еще два типа URL:

URL относительно схемы: //example.org/blog/what-a-wonderful-world
// Twig
{{ url('blog', { post: 'what-a-wonderful-world' }, true) }}

// PHP
$generator->generate('blog', array('post' => 'what-a-wonderful-world'), UrlGeneratorInterface::NETWORK_PATH);

URL относительно пути: ../
// Twig
{{ path('blog', { post: 'what-a-wonderful-world' }, true) }}

// PHP
$generator->generate('blog', array('post' => 'what-a-wonderful-world'), UrlGeneratorInterface::RELATIVE_PATH);

Маршруты, зависящие от хоста

Появилась возможность задавать маршруты для конкретных хостов, и использовать их шаблоны:

user_homepage:
    path: /
    host: "{user}.example.com"
    defaults: { _controller: AcmeDemoBundle:User:profile }

main_homepage:
    path:  /
    defaults: { _controller: AcmeDemoBundle:Main:homepage }

Процессы

Для запуска внешних процессов в дополнение к методу run() (который принимает лямбда-функцию в качестве аргумента для обработки данных) появилась возможность запустить процесс методом start() и получать данные при помощи метода getOutput(). Кроме того, можно воспользоваться методом getIncrementalOutput(), который возвращает новые данные от процесса с предыдущего вызова этого метода.

use Symfony\Component\Process\Process;

$process = new Process('ls -lsa');

$process->run(function ($type, $data) {
    echo $data;
});

Теперь можно так:
use Symfony\Component\Process\Process;

$process = new Process('ls -lsa');

$process->start();

while ($process->isRunning()) {
    echo $process->getIncrementalOutput();
    sleep(1);
}

Добавлены и методы для получения ошибок getErrorOutput() и getIncrementalErrorOutput().

Также расширен список методов получения статуса процесса:
$process->isSuccessful();
$process->hasBeenSignaled();
$process->hasBeenStopped();
$process->isRunning();

// в версии 2.2
$process->isStarted();
$process->isTerminated();


LTS


Теперь ждем первую LTS-версию Symfony 2, которая выйдет в конце мая и будет поддерживаться в течение двух лет.
+9
6988
35
urvalla 5,0

Комментарии (25)

+3
xtech #
Тут обновление, там обновление… эх где бы найти столько времени, чтобы успевать за всеми обновлениями.
0
by25 #
Отличная новость!
0
swwwfactory #
Мне конечно симфони интересна, но вот это совсем уж круче вареных яиц:

use Symfony\Component\PropertyAccess\PropertyAccess;


Это так стандарты PSR-N советуют? Нафиг нафиг такие стандарты. Какая двухсмысленноть и тавтоглогия. Сдается мне все это как хомут следования сомнительным стандартам.

надо было хотя-бы так написать:
namespace symfony\сomponent;

class PropertyAccess
{
...
}




а потом:
use symfony\component;

...
component\PropertyAccess
+1
SowingSadness #
Это соглашение о правиле создания Namespase в Symfony 2.
Если в каком то случае получилось не совсем красиво, то не думаю что нужно сразу об этом кричать.
+1
VolCh #
Двойное PropertyAccess они не советуют прямо, но совпадение имен компонента (последня часть нэймспэйса) и его «главного» класса принято не просто так. Нэймспэйсы транслируются на ФС и классу Symfony\Component\PropertyAccess\PropertyAccess соответствует файл Symfony/Component/PropertyAccess/PropertyAccess.php если мы захотим добавить какой-нибудь служебный класс или интерфейс типа Symfony\Component\PropertyAccess\ObjectAccessor, то весь компонент Symfony\Component\PropertyAccess будет лежать в одном каталоге, а не часть в файле в Symfony/Component/PropertyAccess.php, а часть в файлах Symfony/Component/PropertyAccess/*.php — видал я системы по такому принципу сделанные: очень неудобно, то «главный» файл забудешь скопировать, то каталог, а тут одним махом полностью компонент. Можно, конечно, главный класс назвать не так как компонент (зачастую так и делают), но чаще удобно именно продублировать. Опять же отчасти потому что принято писать полный путь в use, а в new и т. п. только имя класса. Прямых требований вроде нет, но так удобнее.
0
swwwfactory #
Оно понятно что это дань автозагрузчику классов, вычисляющиего по сигнатуре полного имени класса его точный путь, но иногда бывает очень удобно разместить те-же интерфейсные классы прямо в том же файле, где объявлен класс. На практике получается, что в таком модуле могут содержаться (или логично или так хочется) несколько связанных классов Создавать еще один файл ради того чобы туда поместить интерфейс или подкласс Exception занятие утомительное и отвлекающее. Весь компонент может уместиться в одном модуле, если он получается не более 600-700 строк кода.

Решить проблему загрузки классов можно явным указанием статического списка классов, которые в свою очередь при старте будет добавлен к автозагрузчику классов. Есть несколько утилит, генерирующий модуль автолоад всех зависимых классов.
+1
urvalla #
Что же такого отвлекающего и утомительного, особенно при наличии автозагрузчика? Создать файл? Скорее уж утомительно потом лазать по файлу и искать где же нужный класс. Или при необходимости использовать такой второстепенный класс вне основного — автозагрузчик не сработает >> выскочит ошибка >> догадываемся «ага, значит не вынесен класс» >> залезаем в библиотеку, выносим в отдельный файл (или есть альтернативная реальность в которой мы инклудим нужный нам файл, но предположим что мы все-таки хотим пользоваться автозагрузкой) >> и если это самопальный одноразовая библиотека, то радуемся жизни (но что-то не похоже это на упрощенный подход).

Существуют еще версии:
— это n-ая итерация и мы играем в игру «из какого же файла выносить»;
— эта библиотека лежит в репозитории и мы делаем весьма забавный коммит (или еще смешнее, пулл реквест, а если не примут, тогда форк).
0
swwwfactory #
Штатный автозагрузчик по маппингу имен классов делает свою работу относительно хорошо. Но за это приходится платить дублированием имен и довольно приличной вложенностью папок. Это больше похоже на машиночитаемый формат. Кстати, размышляя на эту тему увидел интересную аллегорию с классами и ЧПУ. Что-то в этом есть…

Что же такого отвлекающего и утомительного, особенно при наличии автозагрузчика? Создать файл? Скорее уж утомительно потом лазать по файлу и искать где же нужный класс

Значительно более отвлекает в сравнении, когда все в одном файле. Другой вопрос, что файл может быстро расти, но это стимулирует делать модули компактными.

На мой взгляд, зачем без особой необходимости создавать еще лишние сущности — лучше держать все рядом. Так нагляднее и проще. Особенно это актуально при непосредственной разработке компонента. Да, согласен, что как только классов становится много — такой модуль придется «расшивать», но это обычно уже характерные архитектурные проблемы и антипаттерны. Типа класс слишком много на себя берет. Кстати так легче отследить «точку перегиба», когда число сущностей необоснованно растет.

Насчет «искать где нужный класс» — в общем да, по имени модуля найти класс относительно легко, если не принимать во внимание вложенности папок (нужные классы живут в дремучих лесах). Но обычно это проблема качественной диагностики обработчиком ошибок: имя модуля, класс, строка, ошибка. Когда это работает правильно — локализовать ошибку легко. Есть еще удобное средство поиска git grep, git ls-files рекомендую к использованию.

Существуют еще версии:
— это n-ая итерация и мы играем в игру «из какого же файла выносить»;
— эта библиотека лежит в репозитории и мы делаем весьма забавный коммит (или еще смешнее, пулл реквест, а если не примут, тогда форк).

с этого места не совсем понятно о чем речь — можно более развернуто?
0
urvalla #
Имею ввиду, что если появляется необходимость обращаться к второстепенному классу, когда основной еще не загружен (через автозагрузку) — тут правильно вытащить его в отдельный файл, с его же второстепенными классами (тогда для него автозагрузка заработает). Если такая ситуация несколько раз повторится (т.е. повытаскиваем классы для которых нужна автозагрузка в отдельные файлы), то в каком из файлов лежит нужный (не автозагружаемый) класс будет не очевидно. И по факту, скорее всего, придется еще тестировать все классы на зависимости (вдруг какой-нибудь класс, например, исключения, нужен в нескольких наших файлах — тогда его тоже нужно выносить в отдельный файл для автозагрузки).

А при совместной разработке такой подход может быть особенно неприятен — про это второй пункт.

В общем, совсем маленькую библиотеку, без какого-либо потенциала для развития, и которой никто больше точно не будет пользоваться, еще можно так реализовывать (все в одном файле). Но зачем себя к такому приучать, если придется переучиваться при разработке чего-то более серьезного?
0
swwwfactory #
теперь понятно о чем — благодарю за разъяснения.

Согласен, когда вспомогательный класс расположен совместно где-то в файле с другим классом обнаружить его сложнее. И в случае его обособленного использования — штатный автозагрузчик будет бессилен. Для решения этой проблемы потребуется дополнительная логика автозагрузчику или полная информация обо всех классах в проекте либо, да — использовать правило: один класс один файл.

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

Возможен такой хинт: так как такое бывает не часто, то в случае ошибки автозагрузчик открывает все не открытые модули для данного уровня пространств имен.
0
VolCh #
Дублирование имен модуля и его главного класса дань не столько самому автозагрузчику или IDE, а «менеджеру пакетов», в роли которого частенько выступает человек, которому удобно «пакет — каталог». Хотя, можно было бы позаимствовать концепцию загрузки из python (вроде) и избежать дублирования с помощью файла с предопределенным именем. То есть класс Symfony\Component\PropertyAccess хранился бы в Symfony/Component/PropertyAccess/__main.php, а
Symfony\Component\PropertyAccess\ObjectAccessor в Symfony/Component/PropertyAccess/ObjectAccessor.php или Symfony/Component/PropertyAccess/ObjectAccessor/__main.php на выбор разработчика.

Несколько классов/интерфейсов в одном файле — это другая проблема. Бывает, конечно, желание интерфейс или value object описать в одном файле с основным файлом, но поддержка этого статического списка как-то пугает, пускай и с утилитами. К идее «один класс или интерфейс — один файл c однозначным соответствием имен» я пришёл ещё во времена PHP4. Длинный список require_once 'module_class' с явными зависимостями для каждого используемого класса каждого используемого модуля мне показался более предпочтительный в плане поддержки чем по несколько require_once 'module_submodule' без явного соответствия submodule и имен классов. Хотя, с помощью питоновской системы и это, наверное, можно было решить.
0
swwwfactory #
а «менеджеру пакетов», в роли которого частенько выступает человек, которому удобно «пакет — каталог». Хотя, можно было бы позаимствовать концепцию загрузки из python (вроде) и избежать дублирования с помощью файла с предопределенным именем. То есть класс Symfony\Component\PropertyAccess хранился бы в Symfony/Component/PropertyAccess/__main.php,

Собственно менджера пакетов как раз и не хватает. Вы правильно заметили, что в качестве оного выступает человек. В симфони и PSR, выбран на мой взгляд компромисс между human readable format и программный формат. Но большая вложенность это явный перегиб в сторону программного формата нежели человеко-читаемого + тавтология. Насчет питоновской концепции здравая мысль. Только такое в общем-то уже есть. Называется по другому: вместо __main.php обычно autoload.php, расположенный где-то в известном месте.

К идее «один класс или интерфейс — один файл c однозначным соответствием имен» я пришёл ещё во времена PHP4.

Вы не одиноки в этом — аналогично. Но со временем стало раздражать по каждому чиху создавать отдельный модуль. Нет все держать в одном модуле большее зло — речь идет о разумном компромиссе. С появлением autoload в самом php и spl_autoload все значительно упростилось.

Длинный список require_once 'module_class' с явными зависимостями для каждого используемого класса каждого используемого модуля мне показался более предпочтительный в плане поддержки чем по несколько require_once 'module_submodule' без явного соответствия submodule и имен классов. Хотя, с помощью питоновской системы и это, наверное, можно было решить.

Никаких *_once это только запутывает и усложняет на мой взгляд — все гораздо проще и оптимальнее через одну точку входа на загрузку через spl_autoload.

В идеальном случае include|require сосредоточены только в модулях автозагрузки. Другими словами в прикладном коде нет инклюдов. Это создает некоторую проблему по загрузке чисто функций и просто кода не привязанного к классам, но это решаемо.

0
VolCh #
Называется по другому: вместо __main.php обычно autoload.php, расположенный где-то в известном месте.

Похоже, не поняли меня. Я лишь про изменение конвенции, изменение логики работы общего для всех autoload, во избежание тавтологии. Namespace\Class ищется в Namespace/Class.php или Namespace/Class/__main.php, что позволяет с одной стороны избежать тавтологии, а с другой держать при необходимости основной и вспомогательные классы компонента в одном каталоге.
Никаких *_once это только запутывает и усложняет на мой взгляд — все гораздо проще и оптимальнее через одну точку входа на загрузку через spl_autoload.

Я про PHP4. :)
0
swwwfactory #
насчет мейна после размышлений — в общем оно конечно хорошо, но будет жутко тормозить при проверках. Лучше тогда поменять имя главного класса пакета-компоненты на Main. В этом случае дублирование снимается и все ок.
0
urvalla #
Тавтология — да, поспорить сложно. Но в чем двусмысленность?

Повторение связано с тем, что по PSR-0 namespace соответствует расположению файла, и последний уровень — это название файла.
Symfony\Component\PropertyAccess\PropertyAccess находится в

Symfony/
    Component/
        PropertyAccess/
            PropertyAccess.php
            ...

Что, в целом, логичнее чем:

Symfony/
    Component/
        PropertyAccess.php
        PropertyAccess/
            ...

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

Смысла же в этом не видно совсем:

use Symfony\Component;
...
Component\PropertyAccess

Правда не понимаю чем же такая запись лучше.

По крайней мере, в случае полной записи названия класса в use сразу видны зависимости, а также проще рефакторинг в случае замены класса.
–1
VolCh #
С тавтологией без потери удобства можно было бы бороться чем-то вроде
Symfony/
    Component/
        PropertyAccess/
            __main.php
            PropertyAccess.php


А насчёт второго может быть удобно, если у нас много классов/субнэймспэйсов из Symfony\Component\ используется. Компромисс между полным перечислением в «шапке» и использованием полных имен в коде по читаемости. Хотя с рефакторингом, да, проблемы могут быть, особенно ручным.
0
swwwfactory #
Тавтология — да, поспорить сложно. Но в чем двусмысленность?

На мой взгляд двусмысленность или неоднозначность в том, что сложно отличить имя класса от части namespace Лично меня это запутывает. Когда вложенность namespace большая — приходится задаваться всякий раз вопросом: а нет ли такого класса на данном уровне namespace?

Вы правы. Справедливое замечание. Об полной записи класса, зависимостях и наглядности как-то не подумал. Выработалась привычка писать в основном такое клише:
...
use something\somewhere;
...
$ofoo = new somewhere\Foo();
$obar = new somewhere\Bar();
$obaz = new somewhere\baz\Baz();

Согласен, в таком коде несколько сложнее будет отследить явно имя класса. Но это можно легко компенсировать тегами комментариями на диалекте phpdoc/doxygen. Но так более компактно и проще разрешить конфликты имен.

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

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

Например типа такой структуры: pear2.php.net/PEAR2_Pyrus_Developer/files
Там более прозрачная структура, хотя и большая вложенность присутствует.

–1
swwwfactory #
Там такое сплошь и рядом, доведенное до уровня стандарта. Крика никакого нет, но это реальный бред.
0
Inori #
Вышел апдейт с таким вкусным списком новых фич, а 90% комментариев про какую-то незначительную фигню. Даже не знаю, смеятся или плакать.
0
VolCh #
Самое интересное по идее Console и Process, но область их применения весьма слабо пересекается с обычным кругом задач PHP.
0
urvalla #
Они как раз и помогают расширить этот «обычный круг задач PHP».

А из интересного в непосредственной практике — сразу вот сейчас буду использовать новинки роутинга в Симфони-проектах, PropertyAccess — в старом проекте, который медленно, но верно рефакторю (и явно класс еще не раз пригодится), а Stopwatch — отправляется в дебаг тулкит.
0
VolCh #
В принципе да, но всё равно как-то для консольных утилит PHP мне последний в голову придёт. Вот сижу думаю почему. Наверное из-за слабой поддержки модульности.

А я как-то не вспомнил, где мне нужна была относительная адресация и, главное, зачем :)
0
endshpile #
Routing: Поддержка URL-хостов при маршрутизации; — Отлично

Очень рад развитию этого прекрасного фреймворка, теперь любой более менее сложный проект без этого fw кажется пустым )
0
VolCh #
// промахнулся
0
MaksSlesarenko #
добавте в статью sf2.2 note: _method override disabled by default

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.