Zend_Tool – пакет Zend Framework’a, который позволяет писать свои консольные мини-приложения.
Написать своё подобное приложение достаточно просто, для этого нам потребуется сделать следующее:
- Написать класс провайдера (в нем будет реализована логика приложения)
- Написать класс манифеста (информационный класс, необходим для организации вывода информации о провайдерах)
- “Рассказать” где они ZF’у
Пример реализации провайдера
Начнем с написания простого провайдера:
class My_Tool_Project_Provider_MyProvider extends Zend_Tool_Project_Provider_Abstract implements Zend_Tool_Framework_Provider_Pretendable { public function getName() { // кастомное имя провайдера return 'Myth'; } public function say() { // будет выводить сие приветствие echo "Hello Russian!"; } }
И еще более простого манифеста:
class My_Tool_Project_Provider_Manifest implements Zend_Tool_Framework_Manifest_ProviderManifestable { /** * Возвращаю список всех доступных провайдеров * * @return array */ public function getProviders() { return array( new My_Tool_Project_Provider_MyProvider ); } }
Теперь перед нами стоит задача инициализации данного манифеста и провайдера, для этой цели нам необходимо прописать информацию о них в конфигурационном файле Zend_Tool – .zf.ini (не путать с application.ini):
Создаем новый, используя консоль (будет создан в вашей домашней директории):
zf --setup config-file
Вносим изменения:
basicloader.classes.0 = "My_Tool_Project_Provider_MyProvider" basicloader.classes.1 = "My_Tool_Project_Provider_Manifest"
Расположение файла zf.ini может быть следующим:
- Берется из переменной окружения ZF_CONFIG_FILE
- Загружается файл $homeDirectory/.zf.ini, если таковой имеется
- Загружается файл $storageDirectory/zf.ini, если таковой имеется
Так же необходимо, чтобы эти наши классы лежали в include_path, если это не приемлемо (ну мне уж точно не нравится) – то для этой цели лучше написать утилиту враппер и положить в директорию с zf.sh, вот мой файл zfc.sh:
#!/bin/sh # указываю свой путь к конфигурационному файлу ZF_CONFIG_FILE="./../.zf.ini" export ZF_CONFIG_FILE # добавляю директорию к опции include_path ZEND_TOOL_INCLUDE_PATH_PREPEND="./../library" export ZEND_TOOL_INCLUDE_PATH_PREPEND # вызываю zf.sh с всеми входящими параметрами ./zf.sh $@
Файл zfc.bat для Windows (спасибо Hunter’у):
@ECHO off SET ZF_CONFIG_FILE=./../.zf.ini SET ZEND_TOOL_INCLUDE_PATH_PREPEND=./../library ./zf.bat -- %*
Теперь пробуем вызвать наш провайдер:
cd project/bin ./zfc.sh say myth >> Hello Russian!
В результате всех вышеописанных манипуляций у меня получилась следующая структура проекта:
project |-- application |-- bin | |-- zf.bat | |-- zf.sh | |-- zf.php | |-- zfс.bat | `-- zfс.sh |-- library | |-- My | | `-- Tool | | `-- Project | | `-- Provider | | |-- Manifest.php | | `-- MyProvider.php | `-- Zend |-- public `-- .zf.ini
Провайдер для ZF проекта
Далее стоит привести пример написания провайдеров для интеграции в ZF проект. Начну пожалуй с изменений в структуре проекта:
project |-- application |-- bin |-- library | |-- My | | `-- Tool | | |-- Context | | | `-- My | | | |-- MythDirectory.php (My_Tool_Project_Context_My_MythDirectory) | | | `-- MythFile.php (My_Tool_Project_Context_My_MythFile) | | `-- Project | | `-- Provider | | |-- Abstract.php (My_Tool_Project_Provider_Abstract) | | |-- Manifest.php | | `-- MyProvider.php | `-- Zend |-- public `-- .zf.ini
Теперь приведу листинг каждого файла с небольшими пояснениями. Начну с файла My_Tool_Project_Provider_Abstract – он необходим для регистрации собственных элементов:
require_once 'Zend/Tool/Project/Provider/Abstract.php'; class My_Tool_Project_Provider_Abstract extends Zend_Tool_Project_Provider_Abstract { /** * constructor */ public function __construct() { parent::__construct(); // load My Context elements $contextRegistry = Zend_Tool_Project_Context_Repository::getInstance(); $contextRegistry->addContextsFromDirectory( dirname(dirname(__FILE__)) . '/Context/My/', 'My_Tool_Project_Context_My_' ); } }
Пишем свои элементы – свою директорию:
require_once 'Zend/Tool/Project/Context/Filesystem/Directory.php'; class My_Tool_Project_Context_My_MythDirectory extends Zend_Tool_Project_Context_Filesystem_Directory { /** * @var string */ protected $_filesystemName = 'myth'; public function getName() { return 'MythDirectory'; } }
И некие мифические классы:
class My_Tool_Project_Context_My_MythFile extends Zend_Tool_Project_Context_Zf_AbstractClassFile { protected $_mythName = 'Base'; protected $_filesystemName = 'mythName'; public function init() { $this->_mythName = $this->_resource->getAttribute('mythName'); $this->_filesystemName = ucfirst($this->_mythName) . '.php'; parent::init(); } public function getPersistentAttributes() { return array( 'mythName' => $this->getMythName() ); } public function getName() { return 'MythFile'; } public function getMythName() { return $this->_mythName; } public function getContents() { $className = 'Myth_'.ucfirst(strtolower($this->_mythName)); $codeGenFile = new Zend_CodeGenerator_Php_File(array( 'fileName' => $this->getPath(), 'classes' => array( new Zend_CodeGenerator_Php_Class(array( 'name' => $className, 'methods' => array( new Zend_CodeGenerator_Php_Method(array( 'name' => '__construct', 'body' => '/* Myth Here ... */', )) ) )) ) )); return $codeGenFile->generate(); } }
Теперь надо бы научиться использовать эти элементы, для этого в файле MyProvider добавляем метод create:
public function create($name = "base") { // загружаем профайл проекта (это файл .zfproject.xml) $profile = $this->_loadProfile(self::NO_PROFILE_THROW_EXCEPTION); // ищем директиву projectDirectory $profileSearchParams = array('projectDirectory'); $projectDirectory = $profile->search($profileSearchParams); if (!($projectDirectory instanceof Zend_Tool_Project_Profile_Resource)) { throw new Zend_Tool_Project_Provider_Exception( 'Не нашли - прерываем работу' ); } /* создаем мифическую директорию, и сохраняем данные о ней в профайле */ $mythDirectory = $projectDirectory->search(array('mythDirectory')); if (!($mythDirectory instanceof Zend_Tool_Project_Profile_Resource)) { $mythDirectory = $projectDirectory->createResource('mythDirectory', array('filesystemName' => 'myth')); $mythDirectory -> create(); } /* создаем мифический класс */ /*@var Zend_Tool_Project_Profile_Resource $newMyth*/ /* запись в профайле */ $newMyth = $mythDirectory->createResource( 'mythFile', array('mythName' => $name) ); /* файл в системе */ $newMyth->create(); /* сохраняем профайл */ $this->_storeProfile(); }
Пробуем сие творение:
./zfc.sh create myth Bigfoot
Результатом будем создание директории myth с классом Myth_Bigfoot:
class Myth_Bigfoot { public function __construct() { /* Myth Here ... */ } }
Скачать пример можно по ссылке: My_Tool
Очень круто!
Так можно сделать мега-генератор админки, например, и жить долго и счастливо
Спасибо за статью , нужно будет на досуге написать генератор CRUD так как это сделанно в руби на рельсах.
Хорошая статья!
Может и юнит тесты за одно сгенерировать?
У меня так и не хватило терпения найти причину по которой эта фича не работает “из коробки”.
Так и в чем она оказалась?
В этой статье Антона есть решение этой проблемы?
Проблема, насколько у меня хватило терпения, оказалась в том что Tool, не сканит директорию указанную в .xml как контейнер для провайдеров, возможно проблема в самом деле и не в этом, в багтрекере есть уже упоминание проблемы этой (http://framework.zend.com/issues/browse/ZF-7747 Created: 01/Sep/09 07:57 AM) возможно эта проблема снова платформозавимима, изначально Zend_Tool “из коробки” под виндой вообще не работал, но нет времени все детально проверить.
В статье Антона есть решение но не в том виде в котором мне бы хотелось: в виде .patch для Zend.
Привет!
Решил написать мало ли кому пригодится.
у кого ZF из портов FreeBSD развернут
zfc.sh может так быть
естественно в бин каталоге только zfc.sh
а также используйте класс Zend_Tool_Framework_Client_Console_ResponseDecorator_Colorizer
и маленькая приятная новость, если у функции say указать параметр ($text)
то при вызове провайдера
./zfc.sh say myth
появится приглашение к вводу параметра
Please provide a value for $text
zf>