zfconf 2010: what news zend framework 2.0 brings to us
DESCRIPTION
TRANSCRIPT
Что несёт намZend Framework 2.0?
Надежда Блинова, веб-программист, WizartechГеоргий Туревич, ведущий веб-программист, Wizartech
27 марта 2010 г.Санкт-Петербург
Планируемые изменения
• Изменения в архитектуре• Изменения в MVC• Изменения в стандартах кодирования• Изменения в прочих компонентах
• Пространства имен• __invoke()• Замыкания• Goto• Late Static Binding (LSB) • И др.
Используемые новинки php 5.3
• Унифицированный конструктор • Стандартизация массива Options• Исключения• Контрактное программирование• Уменьшение количества синглтонов• Создание компонентов общего назначения• Новые возможности Php 5.3 в плагинах• Автозагрузка • Пространства имен• goto• Изменения в плагинах
Изменения в архитектуре
Изменения в архитектуреУнифицированный конструктор
public function setOptions($options) { // ... foreach ($options as $key => $value) { $method = 'set' . $key; if (method_exists($this, $method)) { $this->$method($value); } } return $this; }
Во многих компонентах в методе setOptions часто встречается повторяющийся код:
Изменения в архитектуреУнифицированный конструктор
namespace Zend;
// Новый, общий для всех класс Options c методом setOptions class Options { public static function setOptions($object, array $options) { if (!is_object($object)) { return; } foreach ($options as $key => $value) { $method = 'set' . self::_normalizeKey($key); if (method_exists($object, $method)) { $object->$method($value); } } } public static function setConstructorOptions($object, $options) { ... } protected static function _normalizeKey($key) { ... } }
Изменения в архитектуреУнифицированный конструктор
namespace Zend;
class Options { public static function setOptions($object, array $options) { ... } public static function setConstructorOptions($object, $options) { if ($options instanceof Zend_Config) { $options = $options->toArray(); } if (is_array($options)) { self::setOptions($object, $options); } } protected static function _normalizeKey($key) { $option = str_replace('_', ' ', strtolower($key)); $option = str_replace(' ', '', ucwords($option)); return $option; } }
Изменения в архитектуреУнифицированный конструктор
use Zend\Options as Options; class Foo { public $bar = ''; public $baz = '';
public function __construct($options = null) { Options::setConstructorOptions($this, $options); } public function setOptions(array $options) { Options::setOptions($this, $options); } public function setBar($value) { $this->bar = $value; } public function setBaz($value) { $this->baz = $value; } }
Изменения в архитектуреУнифицированный конструктор
$foo = new Foo(array('bar' => 'baz')); echo $foo->bar; // "baz" $foo->setOptions(array('bar' => 'boo', 'baz' => 'bat')); echo $foo->bar . $foo->baz; // "boobat"
• underscore_separated_keys
• camelCasedKeys
• UPPERCASEDKEYS
• lowercasedkeys
• all_lowercase_underscore_keys
Переводим в camelCase:
Изменения в архитектуреСтандартизация массива Options
str_replace(' ', '', ucwords(str_replace('_', ' ', $value)));
Изменения в архитектуреExceptions. Исключения
// у каждого компонента должен быть свой интерфейс Exceptionnamespace \Foo\Bar; interface Exception { } class InvalidArgumentException extends \InvalidArgumentException implements Exception { } try { throw new InvalidArgumentException(); } catch (\Foo\Bar\Exception $e) { }
Design By Contract
формальные, точные и верифицируемые интерфейсы
• Минимальные требования к компонентам будут вынесены в интерфейсы.
• С интерфейсами будут предложены стандартные реализации
• Разработчик сможет расширять стандартные и создавать кастомные реализации на основе интерфейса
Изменения в архитектуреКонтрактное программирование
Почему стоит отказаться от синглтона:
• Глобальное состояние усложняет разработку и тестирование
• Зависимость обычного класса от синглтона не видна в публичном контракте класса
• Наличие синглтона понижает тестируемость приложения в целом и классов, которые используют синглтон, в частности
Изменения в архитектуреУменьшение количества синглтонов
• Plugins/Helpers/Strategies
• Decorators
• Factories
• Caching
Изменения в архитектуреСоздание компонентов общего назначения
Области дублирующегося кода будут вынесены в отдельные подключаемые файлы
• __invoke()
Изменения в архитектуреНовые возможности Php 5.3 в плагинах
class Example { public function __invoke() { echo "Hello World! \n"; } } $foo = new Example; $foo();
• Closures (Замыкания)
Изменения в архитектуреНовые возможности Php 5.3 в плагинах
Нотация:
function ($var) use ($outerVar1, $outerVar2, ...) {}
Пример:
function outer($x) //Определение внешней функции
{ $y = 2; //Локальная переменная внешней функции $inner = function ($a) use ($x, $y) //Определение внутренней функции { $b = 4; //Локальная переменная внутренней функции $res = $x + $y + $a + $b; echo $res; //Результат будет равен 10 }; $inner(3); //Вызов внутренней функции
}
outer(1);
• Pros:– Польза для фреймворка в целом и Zend_Controller и
Zend_Search_Lucene в частности– Создание отдельного пространства для unit тестирования: \
Test\Zend, \Zend\Test, или \ZendTest
• Cons– Глобальное переписывание кода
Изменения в архитектуреПространства имен
• Полезно при работе с конечными автоматами (FSM - Finite State Machine) – парсерами
– синтаксическими / лексическими анализаторами
• Компоненты: – Zend_Controller_Front
– Zend_Ical
– Zend_Search_Lucene
– Zend_Markup
– …
Изменения в архитектуреИспользование goto
Три стандартных типа:• Chains
(валидаторы, фильтры, декораторы)• Helpers
(action helpers, view helpers)• Adapters
(form display groups, subforms и элементы; database adapters; translation adapters и др. )
Изменения в плагинах
• Отсутствие единообразия имен
• Разнообразие вызовов:– Action helpers: direct() – View helpers: formSelect() (имя класса)– Validators: isValid() – Filters: filter()
• Снижение производительности: PluginLoader производит лишние операции при каждом поиске.
• Нет единой парадигмы для конструкторов и/или конфигураций адаптеров
• Нет единой парадигмы для передачи информации фабрикам
Изменения в плагинахПроблемы с плагинами
• Использование пространств имен$loader->registerNamespace('My\Validators'); $class = $loader->load('foo'); //My\Validators\Foo
- un/registerNamespace() вместо addPrefixPath() - class_exists()
• Использование имен-через-дефис-в-нижнем-регистре $loader->registerNamespace('My\Validators'); $class = $loader->load('foo-bar'); //My\Validators\FooBar
• Игнорирование нижнего подчеркивания поиском плагинов
Изменения в плагинахРекомендации: именование плагинов
Предполагается, что хэлперы будут всегда использовать __invoke()
Изменения в плагинахРекомендации: использование __invoke()
При необходимости __invoke() должен переадресовывать на другой метод
interface Validator { public function isValid($value, $context = null); public function __invoke($value, $context = null); }class FooValidator implements Validator { public function isValid($value, $context = null) { // ... } public function __invoke($value, $context = null) { return $this->isValid($value, $context); } }
Пример использования:
Изменения в плагинахРекомендации: использование __invoke()
$validator = new \My\Foo\Validator();
//новый вызовif ($validator($value)) {}
//аналогично старому вызовуif ($validator->isValid($value)) {}
Все адаптеры должны реализовывать интерфейс "Configurable".
Изменения в плагинах Рекомендации: реализация интерфейса Configurable
interface ConfigurableInterface { public function setOptions($options); }
class Adapter implements ConfigurableInterface { public function setOptions($options) { // ... настраиваем объект ... } }
Фабрики будут создавать объект адаптера и передавать ему опции.
Изменения в плагинах Рекомендации: реализация интерфейса Configurable
class ValidatorFactory { public static function factory($options) { if ($options instance of \Zend\Config) { $options = $options->toArray(); } if (!is_array($options)) { throw new InvalidArgumentException(); } $adapter = new $options['type']; $adapter->setOptions($options['params']); return $adapter; } }
Фабрики должны иметь связанный с ними загрузчик плагинов и позволять подключать другие загрузчики.
• Цепочки должны расширять один из классов компонента pubsub:– Provider– FilterChain
• Построение стандартных цепочек везде, где это доступно, например, в декораторах
• Расширение цепочек и добавление подклассов к объектам, которые их подключают
• Цепочки должны вести себя как загрузчики плагинов, либо подключать загрузчики плагинов
Изменения в плагинахРекомендации: цепочки (Chains)
• Model-View-Controller (MVC, «Модель-представление-поведение», «Модель-представление-контроллер») — архитектура программного обеспечения.
• Шаблон MVC позволяет разделить данные, представление и обработку действий пользователя на три отдельных компонента– Модель (Model)
Модель предоставляет данные и реагирует на запросы, изменяя свое состояние.
– Представление (View)Отвечает за отображение информации (пользовательский интерфейс).
– Поведение (Controller)Интерпретирует данные, введенные пользователем, и информирует модель и представление о необходимости соответствующей реакции.
Новая реализация MVC
• Zend_Controller 2.0• Zend_Controller_Router 2.0• Zend_View 2.0• Zend_Session 2.0• Zend_Form 2.0
Новая реализация MVC
• Цели рефакторинга:
После всех изменений компонент должен стать:– небольшим– гибким– свободно расширяемым– легким в создании и использовании кастомных
реализаций
Zend_Controller 2.0
Две методики :• конечный автомат (FSM)• событийная модель (Event-driven model)
Zend_Controller 2.0
• Реализация похожа на dojo pubsub
• PubSub == Publish/Subscribe, организация взаимодействий по подписке
• Основные методы: – publish()– subscribe()
Zend_Controller 2.0Event-driven Model: PubSub
Zend_Controller 2.0Event-driven Model: PubSub
class TestPubSub { public function someFunction($message) { echo('someFunction: ' . $message); } public function anotherFunction($message) { echo('anotherFunction: ' . $message); } }
$classObject = new TestPubSub(); $providerObject = new Provider();
$outerFunction = function ($message) { echo('outerFunction: ' . $message); };
Zend_Controller 2.0Event-driven Model: PubSub
$someFunctionHandle = $providerObject->subscribe( 'mvc.routing', $classObject, 'someFunction'); $anotherFunctionHandle = $providerObject->subscribe( 'mvc.routing', $classObject, 'anotherFunction'); $outerFunctionHandle = $providerObject->subscribe( 'mvc.routing', $outerFunction);
$providerObject->publish('mvc.routing', 'Join the Dark Side!'); $providerObject->publish('mvc.routing', 'We have cookies!');
$providerObject->unsubscribe($someFunctionHandle); echo('--- Производим unsubscribe функции someFunction ---'); $providerObject->publish('mvc.routing', 'Join the Dark Side!'); $providerObject->publish('mvc.routing', 'We have cookies!');
Zend_Controller 2.0Event-driven Model: PubSub
someFunction: Join the Dark Side! anotherFunction: Join the Dark Side! outerFunction: Join the Dark Side!
someFunction: We have cookies!another Function: We have cookies!outer Function: We have cookies!
--- Производим unsubscribe функции someFunction ---
anotherFunction: Join the Dark Side! outerFunction: Join the Dark Side!
another Function: We have cookies!outer Function: We have cookies!
• Событийная модель:
– Определяется 4 состояния: routing, dispatching, response, error
– Методам передается событие (Event)
– Методы изменяют состояние события
– Вызывающий метод проверяет состояние и реагирует на
изменение.
Zend_Controller 2.0Event-driven model
Zend_Controller 2.0Event-driven model
routing: ...
dispatching: $pubsub->publishUntil($stateChanged, 'mvc.dispatching.pre', $e); $pubsub->publishUntil($stateChanged, 'mvc.dispatching', $e); $pubsub->publishUntil($stateChanged, 'mvc.dispatching.post', $e);
$e->setState('response');
response: ...
error: $pubsub->publishUntil($stateChanged, 'mvc.error', $e);
Определяется 4 состояния :
Zend_Controller 2.0Event-driven model
//$stateChanged – замыкание, проверяющее состояние событияdispatching: $e->markState(); $pubsub->publishUntil($stateChanged, 'mvc.dispatching.pre', $e); if ($e->isStateChanged()) { goto switchState; }
$pubsub->publishUntil($stateChanged, 'mvc.dispatching', $e); if ($e->isStateChanged()) { goto switchState; }
$pubsub->publishUntil($stateChanged, 'mvc.dispatching.post', $e); if ($e->isStateChanged()) { goto switchState; }
$e->setState('response');
Zend_Controller 2.0Event-driven model
switchState: switch ($e->getState()) { case 'routing': goto routing; case 'dispatching': goto dispatching; case 'response': goto response; case 'error': goto error; default: throw new StateException(); }
• Объект Request • Объект Response • Объект Renderer • Объект Router • Объект Dispatcher • Action controllers • Объект ErrorHandler
Zend_Controller 2.0Дополнительные компоненты
• Плагины фронт контроллера подписываются только на нужные темы
• Action helper broker может быть встроен в объект Event
• Объект View станет зависимым от рендерера, который, в свою очередь, подчинен объекту Responce.
• Остается возможность внедрить собственные реализации
• Достигается большой выигрыш производительности
Zend_Controller 2.0Последствия
• Сейчас по умолчанию не производится никакой фильтрации приходящих данных
• Планируется добавить фильтрацию/валидацию по умолчанию для суперглобальных массивов
• К суперглобальным массивам без фильтрации обращаться через методы getRaw*()
Zend_Controller 2.0Poka Yoke фильтрация
• Передача ошибки поиска action из __call в noRouteAction()
• Использование оверлоадинга для доступа к action helpers: __call() будет изменен.
Zend_Controller 2.0Список изменений в Action Controller
//вызов через HelperBroker $this->_helper->redirector('index'); //превратится в $this->redirector('index');
//доступ к свойствам $this->_helper->viewRenderer->setNoRender(true); //станет таким $this->viewRenderer->setNoRender(true);
• легкость• контрактное программирование• роутер будет работать с объектами запросов• будут исправлены те ошибки, которые сложно
исправить без нарушения обратной совместимости
Zend_Controller_Router 2.0
• Используется реализация "Horde Routes"• Метод getInstance() предполагается удалить• getDefault() и getDefaults() могут стать методами
интерфейса• Метод match() будет принимать только объект Zend\
Controller\Request\Http• Построение маршрутов через конфиг• Цепочки• Маршрут Hostname• Поддержка кэширования
Zend_Controller_Router 2.0Изменения в маршрутах
• Поиск совпадений по дереву (tree-matching).
• В дереве ищется только одно совпадение.
• Достаточно всего раз отыскать имя хоста вместо поиска его для каждого нового маршрута.
Zend_Controller_Router 2.0Chains
• Из объекта HTTP Request будет создаваться уникальный ключ
• Найденный результат сохранится.
• Каждый отдельный маршрут будет достаточно найти единожды.
Zend_Controller_Router 2.0Поддержка кэширования
Проблемы текущей реализации:
• Одновременная реализация как логики Модели (фильтрация, валидация, метаданные), так и логики Вида (рендеринг, декораторы)
• Использование объектов формы для валидации
• Неоднозначная реализация системы декораторов
Zend_Form 2.0
• Изменение form*() view helpers так, чтобы они могли принимать элементы или другие объекты Zend_Form
Zend_Form 2.0Рекомендации: рефакторинг view helpers
Zend_Form 2.0
$element->setOptions(array( 'size' => 25, 'maxlength' => 140, 'class' => 'form-text', )); echo $view->formText($element);
Работать с декораторами можно, используя метод PubSub filter():
Zend_Form 2.0Рекомендации: использование PubSub
Будут предложены готовые цепочки:
// Учитывая, что Zend\Form\DecoratorChain // наследует Pubsub\FilterChain // и что render() переадресует на filter(): $chain = new Zend\Form\DecoratorChain(); $chain->subscribe('Zend\Form\decorator\Label'); $chain->subscribe('Zend\Form\decorator\ViewHelper'); $chain->subscribe('My\Decorator\Div'); $chain->setView($view); echo $chain->render($element);
$chain = new Zend\Form\Decorator\DefinitionListChain; $chain->setView($view); echo $chain->render($element);
Использование PubSub как базы для цепочек фильтров и валидаторов позволит их присоединять и отсоединять.
Zend_Form 2.0Рекомендации: использование PubSub
$vChain = new Zend\Validator\ValidatorChain(); $vChain->subscribe('Int'); $vChain->subscribe('MinLength', array(3)); $vChain->subscribe('MaxLength', array(20));
// прекращение валидации на первой ошибке подписчика$vChain->breakOnFailure(true); $element->setValidatorChain($vChain);if ($element->isValid($values)) { … } $fChain = new Zend\Filter\FilterChain(); $fChain->subscribe('StringTrim'); $element->setFilterChain($fChain); $newValues = $elemen->filter($values);
Для полного разделения View и Model нужно доставать цепочки из формы для использования в модели – и наоборот:
Zend_Form 2.0
// Извлекаем ранее созданные цепочки фильтров и валидаторов // всех элементов формы $filterChain = $form->getFilterChain(); // Присоединяем к модели: $model->setFilterChain($filterChain);
Рекомендации: работа с цепочками фильтров и валидаторов
Объектная структура:
Zend_Form 2.0
Zend\FilterChain::__set_state(array( "foo" => Zend\FilterChain\Element::__set_state(array( 'filterChain' => Zend\Filter\FilterChain(array( '_subscribers => array( 'StrimTrim', ), )), 'validatorChain' => Zend\Filter\ValidatorChain(array( '_subscribers => array( array('Int'), array('MinLength', array(3)), array('MaxLength', array(25)), ), )), )), ))
Рекомендации: работа с цепочками фильтров и валидаторов
Zend_Form 2.0
Будет использоваться так:
$chain->isValid($values); $value = $chain->get($key); $chain->set($key, $value); $chain->setAll($values); $elementChain = $chain->getChain($key);
Рекомендации: работа с цепочками фильтров и валидаторов
Все классы форм могут определять свойство "metadata", содержащее пары ключ/значение, и набор методов set/get для метаданных
Zend_Form 2.0 Рекомендации: разделение между метаданными и поведением
$form->setMetadata(array( 'action' => $url, 'method' => 'post', 'id' => 'registration',
)); $element->setMetadata(array( 'class' => 'form-text',
'size' => 25, 'maxlength' => 140, )); $element->setName('foo');
• Сейчас все элементы Zend_Form генерируют id. • Отмена генерации id решит целый ряд проблем с UI.
• Перевод можно перенести в слой view. • Цепочки View и Decorators будут получать объект
Translator.
Zend_Form 2.0 Рекомендации: отмена установки id, перевод
• Конфигурация: создание цепочек из конфигурации больше не будет доступно.
• Метаданные: отдельный контейнер "metadata" может повлечь проблемы с уже существующими конфигурациями.
• Удаление ID: Удаление генерации id может повлечь за собой проблемы с UI. Эта особенность, возможно, будет конфигурируемой.
Zend_Form 2.0Проблемы обратной совместимости
• В Zend Framework 1.x компонент Zend_Session является труднотестируемым.
• Планируется:– сделать Zend_Session более тестируемым– добавить возможность инъекции массива сессии– Zend_Session больше не будет синглтоном
Zend_Session 2.0
• Планируется:– реализация компонента упростится и станет гибче– добавятся брокеры для управления фильтрами и
хэлперами– благодаря изменениям в MVC переменные будут
передаваться во view script по токену
Zend_View 2.0
• Список других элементов:– Zend_Http_Client 2.0– Zend_Soap 2.0– Zend_Mail 2.0
• Планируется улучшить, переработать или внести улучшения / исправления багов, требующие BC breaks
• Подробнее: http://framework.zend.com/wiki/display/ZFDEV2/Home
Другие элементы
GIT
• ACL не накладывает ограничений. Каждый разработчик сможет создать локальную копию репозитория
• Требования к пропускной способности и объему хранилища ниже
• Упрощается разделение на ветки
• Возможность организовать рабочие процессы, отличающиеся от рабочих процессов SVN
GIT вместо Subversion
Стандарты кодированияПсевдонимы пространств имен
use Zend\Pubsub\Provider as Provider;
Псевдонимы (alias) назначаются при использовании "use":
Шаблоны:
• Делая псевдонимом последнюю часть пространства имен, можно просто не использовать нотацию "as Something"
use \Zend\Filter; // Псевдонимом будет "Filter" use \Zend\Form\Element; // Псевдонимом будет "Element"
• Псевдоним для класса:или использовать имя классаили подставлять суффиксом пространство имен класса
// Псевдоним "HelperBroker"use \Zend\Controller\Action\HelperBroker; // Добавляя суффикс use \Zend\Filter\Int as IntFilter;
• Абстрактные классы– имена абстрактных классов будут предваряться словом "Abstract" :
AbstractController, AbstractForm, и т.п.
• Интерфейсы– должны именоваться описательно и достаточно обобщенно – не должны конфликтовать– должны четко указывать на задачи интерфейса
ConfigurableAdaptableResourceRoleLoadableBuilder
• Указания типов– должны использовать доступные псевдонимы везде, где возможно– если имя класса появляется в области видимости всего раз, ему
тоже рекомендуется задать псевдоним
Стандарты кодирования
• Надежда Блинова: [email protected]• Георгий Туревич: [email protected]
Вопросы?