NetPlant — простая хостинговая панель
В силу необходимости, решил написать свою хостинговую панель для shared хостинга.
Почему свою? Всё просто.
Ни одна из тех панелей, что я нашел, не умеет:
- Работать с несколькими серверами из одной панели
- Давать возможность создавать сайты со своими конфигами
- Работать в конфигурации nginx+php-fpm, nginx+apache одновременно
Результат моего 2.5 дневного творения уже можно пощупать на github: https://github.com/DevGroup-ru/NetPlant.
Пока что умеет только создавать сайты на серверах из конфигов, но лично мне это уже хорошо упрощает жизнь.
Основано на:
- Yii framework 1.x
- Предыдущих наработках для движка CMS
- Интерфейс - twitter bootstrap (via YiiBooster)
По поводу лицензии пока не задумывался - сначала надо довести до более-менее стабильной версии.
Необычное проявление ошибки failed to open stream: No such file or directory
В Yii Framework, как и во многих других фреймворках, принято подгружать классы стандартной для PHP автозагрузкой.
Сегодня наткнулся на такой вот странный баг, проявившийся при разворачивании ранее рабочего сайта на другом сервере:
PHP Error [2]
include(EasmSelectEx.php): failed to open stream: No such file or directory (/Users/bethrezen/Documents/DotPlant-private/yii-framework/framework/YiiBase.php:423)
#0 /Users/bethrezen/Documents/DotPlant-private/yii-framework/framework/YiiBase.php(423): DotPlantWebApplication->handleError()
#1 /Users/bethrezen/Documents/DotPlant-private/yii-framework/framework/YiiBase.php(423): autoload()
#2 unknown(0): autoload()
#3 /Users/bethrezen/Documents/DotPlant-private/yii-framework/framework/web/CWidgetFactory.php(148): spl_autoload_call()
#4 /Users/bethrezen/Documents/DotPlant-private/yii-framework/framework/web/CBaseController.php(147): CWidgetFactory->createWidget()
#5 /Users/bethrezen/Documents/DotPlant-private/yii-framework/framework/web/CBaseController.php(173): AsmSelect->createWidget()
#6 /Users/bethrezen/Documents/DotPlant-private/protected/modules/User/widgets/AsmSelect.php(75): AsmSelect->widget()
Казалось бы проблема в отсутствующем файле, но это не так. Файл присутствует и права на месте.
Проблема же была в том, что в этом файле использовались short tags - <?
вместо <?php
.
В общем, странно, непредсказуемо, но логично. Ещё раз доказывает, что короткие теги не нужны.
Yii: CStatRelation и defaultScope у реляций
Ночью столкнулся с внезапной проблемой в Yii Framework 1.
Оказывается, статистические запросы через CStatRelation не поддерживает scopes в параметрах. Так что, если мы имеем defaultScope в нашем связанно элементе, то отменить его действие мы не сможем.
Как повторить эту проблему. Допустим имеем класс RevisionsSets с таким вот определением реляций:
/** * @return array relational rules. */ public function relations() { return array( 'pricesCount'=>array( self::STAT, 'Revisions', 'revisionSetId', ), ); } |
Ну и в самом классе Revisions имеем следующую группу условий по-умолчанию:
public function defaultScope() { $rev = Config::model()->getConfigValue("Shop", "CurrentRevision")->value; $t = $this->getTableAlias(false, false); return array( 'condition' => "$t.revisionSetId=:rev", 'params' => array('rev'=>$rev), ); } |
Что же происходит при вызове $revisionsSetModel->pricesCount
?
STAT-реляция забирает количество записей, но при этом применяет defaultScope от модели Revisions и в итоге для всех экземпляров RevisionsSets считается некорректное количество записей.
Исправить такое поведение можно было бы допливанием CStatRelation и реализацией там поддержки условий, как это например сделано в HAS_MANY, MANY_MANY, BELONGS_TO.
Но мне в голову пришел более быстрый вариант. Я создал класс RevisionsResetScope, который наследует Revisions и переопределяет defaultScope:
<?php class RevisionsResetScope extends Revisions { public function defaultScope() { return $this->resetScope(); } } |
Этот класс и используем в реляции:
/** * @return array relational rules. */ public function relations() { return array( 'pricesCount'=>array( self::STAT, 'RevisionsResetScope', 'revisionSetId', ), ); } |
Вот такой костыль. Кстати, на форуме Qiang Xue решил отказаться от статистических запросов и CStatRelation в Yii Framework 2. Будем надеяться, что разработчики предусмотрят достойную замену.
Создаём расширяемую структуру на Yii Framework
Хочу предложить Вашему вниманию концепт расширяемой структуры приложения на Yii Framework. Я уже писал как сделать свои Actions на Yii.
Сегодня же речь пойдёт о написании стандартных экшенов для контроллеров и их расширения.
Предположим, в приложении у нас все контроллеры наследуются от класса dotPlantFrontMainController.
Нам необходимо, чтобы у всех контроллеров было действие Breadcrumb.
Для этого, в базовом контроллере dotPlantFrontMainController переопределяем функцию actions() следующим образом:
public function actions() { return array( 'breadcrumb' => 'application.extensions.actionBreadcrumb', ); } |
Если в унаследованном контроллере(скажем News) мы используем экшены из других файлов, то нам необходимо также переопределить функцию примерно вот так:
public function actions() { // return external action classes, e.g.: return array_merge( parent::actions(), array( 'admin'=>'application.controllers.News.ActionAdmin', ) ); } |
Таким образом, в News будут экшены определенные в базовом классе dotPlantFrontMainController плюс ActionAdmin.
Переписать же actionBreadcrumb можно уже в теле самого News.
В итоге имеем легко расширяемую структуру. В самом же базовом классе массив actions можно автоматически генерировать в соответствии с установленными расширениями. Всё легко и просто 🙂
Yii — создание своих типов Action-ов
Пишу одно прлиожение на Yii Framework. Структура приложения такова, что необходимо разграничить экшены контроллеров(CController) по папкам и сделать их типовыми.
Экшены разграничиваются легко.
Предположим, что у нас есть ряд экшенов для администрирования. Всем им надо установить какие-либо свойства или же сделать при их вызове какие-то типовые действия.
Рассмотрим на примере изменения титла страницы.
Вот код моего базового экшена:
class dotPlantAdminAction extends CAction { /** * Constructor. * Runs CAction constructor and makes some changes for Admin action type */ public function __construct($controller,$id) { parent::__construct($controller,$id); $this->controller->pageTitle = dotPlant::getName() . " - Admin "; } public function run() { } } |
В конструкторе мы сначала вызываем parent::__construct с необходимыми параметрами. Это грубо-говоря идёт инициализация базового CAction.
Допустим у нас есть контроллер SettingsController. И нам нужно сделать его на основе нашего экшена. Создаем папку protected/controllers/Settings/. В ней создаем файл ActionAdmin.php с таким вот кодом:
class ActionAdmin extends dotPlantAdminAction { public function run() { $this->controller->render('admin'); } } |
Видите? Нам достаточно только объявить run() и уже всё готово. При загрузке запустится необходимый нам конструктор базового контроллера с нашими действиями.
Вот по такому принципу я разграничиваю например админку в своей CMS dotPlant на Yii Framework. Слава PHP пятому!