image
После двух лет разработки закончена третья версия фреймворка PHPixie. Почему так долго? На самом деле за это время было написано не меньше трех ORM и шаблонизаторов, которые удалялись и переписывались опять, потому что «ааа, можно ведь сделать лучше». Особенно много времени ушло на тесты, без которых огромное количество улучшений просто не было бы замечено. Много раз хотелось просто оставить это дело, остановиться на второй версии и добавлять в нее модули. Но сейчас, когда все эти итерации были пройдены я могу уверенно сказать что это лучшая имплементация которую я знаю ( и на какую был способен ). Вот чем вас порадует PHPixie 3:

  • Следование стандартам PSR-2 и PSR-4
  • Поддержка PSR-7 запросов и библиотека для удобной работы с ними
  • Шаблонизатор использующий простой PHP, но с поддержкой наследования и блоков как у Twig. Позволяющий легко добавлять свои расширения и другие форматы, например HAML итд.
  • ORM который прост в использовании как ActiveRecord, но при этом разбивающий логику запросов, сущностей и репозиториев отдельно. Поддерживающий связи с коллекциями MongoDB и оптимизацию запросов над многими сущностями одновременно (например можно связать несколько статей с несколькими тэгами одним запросом)
  • Подход с процессорами вместо привычных контроллеров позволяет создать произвольную архитектуру.
  • Компонент конфигураций позволяющий разбивать настройки по в глубину по папкам (например ключ languages.en.plural.mouse может обратится к ключу plural.mouse в файле languages/en.php)
  • Система бандлов позволяющая легко использовать один код в нескольких проектах и делится ним с другими пользователями. Бандлы устанавливаются через композер как любая другая библиотека.


А сейчас короткий туториал, который покажет вам все что надо знать чтобы начать разработку с PHPixie 3:


Инсталляция

Сначала создаем скелет проекта используя Composer:

php composer.phar create-project phpixie/project your_project_folder 3.*-dev


Настройте ваш HTTP сервер на папку /web directory. В случае Nginx вам также понадобится добавить это в конфиг:

location / {
            try_files $uri $uri/ /index.php;
}


Если вы используете Windows вам надо будет создать ярлык /web/bundles/app указывающий на /bundles/app/web.
На Linux и MacOS ярлык будет работать из коробки. Он нужен для того чтобы открыть доступ к веб файлам дефолтного бандла (сейчас все объясню).

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

Бандлы

PHPixie 3 поддерживает систему бандлов, как например делает Symfony2. Если вы еще с ними не работали представьте себе что сайт можно теперь разбить на логические части, которые потом легко переносить в другие проекты и делится через Composer. Например аутентификация пользователей может быть создана как отдельный бандл со своими шаблонами, стилями и изображениями а затем использована на нескольких проектах. Бандлы «монтируются» в проект как например в линуксе диски монтируются в файловою систему.

По началу проект создается с единым бандлом 'app' в '/bundles/app'. И хоть структура проекта в третьей версии кажется сложнее чем во второй, но пока у вас только один бандл вы редко будете делать что-то вне этой директории.

Процессоры

Привычная концепция MVC Контроллеров сильно расширена в PHPixie, она теперь поддерживает бесконечную вложенность и произвольную архитектуру. Идея в том что «контроллер» с точки зрения фреймворка есть только один. Но он может например делегировать свои функции «субконтроллерам» и вообще делать что ему захочется. Интерфейс процессора ( как они теперь называются) состоит всего из одного метода process($request). Это позволило создать несколько базовых классов процессоров, которые по разному обрабатывают запросы. Конечно-же один из вариантов полностью похож на привычные контроллеры, так что можно все делать по-старинке, что и делает стандартный Greet процессор, который показал нам приветствие после установки.

Теперь создадим новый процессор Quickstart, на котором и будем все пробовать:

// bundles/app/src/Project/App/HTTPProcessors/Quickstart.php

namespace Project\App\HTTPProcessors;

use PHPixie\HTTP\Request;

//  Расширяем класс который действует как привычный контроллер
class Quickstart extends \PHPixie\DefaultBundle\Processor\HTTP\Actions
{
    /**
     * Builder будет использоваться повсюду
     * чтобы достукаться в разные части фреймворка
     * @var Project\App\Builder
     */
    protected $builder;
    
    public function __construct($builder)
    {
        $this->builder = $builder;
    }
    
    // Дефолтное действие
    public function defaultAction(Request $request)
    {
        return "Quickstart tutorial";
    }
    
    //Уже скоро мы добавим сюда новых методов
}


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


// bundles/app/src/Project/App/HTTPProcessors.php

//...
    protected function buildQuickstartProcessor()
    {
        return new HTTPProcessors\Quickstart(
            $this->builder
        );
    }
//...


Зайдя на localhost/quickstart теперь мы увидим сообщение «Quickstart tutorial».

Теперь можно попробовать другие части фреймворка.

Роутинг

Часто приходится создавать ссылки типа /quickstart/view/4 включающие id или имя страницы или товара. Для этого сначала создадим простенькое действие в нашем процессоре:

// bundles/app/src/Project/App/HTTPProcessors/Quickstart.php

//...
    public function viewAction($request)
    {
        //Выведем параметр 'id' 
        return $request->attributes()->get('id');
    }
    
//...
}


Теперь так же понадобится добавить правило с этим параметром к тем что уже прописаны в конфиге. Но сначала рассмотрим как он выглядит:

// bundles/app/assets/config/routeResolver

return array(
    // тут описана группа роутов
    // который будут пробоваться
    // пока какой-то не подойдет 
    'type'      => 'group',
    'resolvers' => array(
        
        //...вот сюда мы добавим наши модификации
        
        //Дефолтный роут
        'default' => array(
            'type'     => 'pattern',
            
            // скобки обозначают необязательную часть
            'path'     => '(<processor>(/<action>))',
            
            //Дефолтные параметры
            //Например если ссылка просто /greet
            //То параметр 'action' будет 'default'
            'defaults' => array(
                'processor' => 'greet',
                'action'       => 'default'
            )
        )
    )
);


Часть которую мы хотим добавить выглядит вот так:

'view' => array(
    'type'     => 'pattern',
    
    //Поскольку параметр id обязателен
    //то скобок не ставим
    'path'     => 'quickstart/view/<id>',
    'defaults' => array(
        'processor' => 'quickstart',
        'action'    => 'view'
    )
)


Поскольку роуты подбираются по порядку то важно чтобы более специфические правила были в конфиге выше чем общие.

Теперь зайдя на localhost/quickstart/view/5 вы увидите '5' в ответ.

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

array(
    
    //Создаем префикс
    'type'      => 'prefix',
    
    // У префикса может быть свой паттерн
    // со своими параметрами
    'path'   => 'user/<userId>/',
    'resolver' => array(
        'type'      => 'group',
        'resolvers' => array(
        
            //направит /user/5/friends to Friends::userFriends()
            'friends' => array(
                'path'  => 'friends',
                'defaults' => array(
                    'processor' => 'friends',
                    'action'    => 'usersFriends'
                )
            ),
            
            //направит /user/5/profile to Profile::userProfile()
            'profile' => array(
                'path'  => 'profile',
                'defaults' => array(
                    'processor' => 'profile',
                    'action'    => 'userProfile'
                )
            )
        )
    )
);



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

Ввод и вывод

Как вы уже заметили, каждое действие получает запрос как параметр и возвращает какой-то ответ. Вот как мы можем получить разную информацию из запроса:

//$_GET['name'] 
$request->query()->get('name');

//$_POST['name'] 
$request->data()->get('name');

//Параметры роутинга
$request->attributes()->get('name');


А теперь немного интереснее:

$data = $request->data();

// С заданием дефолтного значения
$data->get('name', 'Trixie');

// Выбросит исключение
// если параметр 'name' не задан
$data->getRequired('name');

// Получение вложенного поля
// $_POST['users']['pixie']['name']
$data->get('users.pixie.name');

// Можно также 'разрезать' параметры 
// чтобы не писать долгие ключи много раз
$pixie = $data->slice('users.pixie');
$pixie->get('name');

// Получить данные простым массивом
$data->get();

// Получить массив ключей
$data->keys();

// А можно сразу итерировать
// как по массиву
foreach($data as $key => $value) {

}

// Кстати если вам нравится такой синтаксис
// посмотрите на библиотеку phpixie/slice
// которую можно использовать с любыми массивами


JSON запросы тоже автоматически парсяться в $request->data(), что уже облегчает работу с AJAX запросами


Вывод еще проще:

// Просто текст
return 'hello';

// Чтобы вывести JSON
// просто возвращаем массив или объект
return array('success' => true);

// Или строим произвольные ответы
// используя библиотеку HTTP
$http = $this->builder->components()->http();
$httpResponses = $http->responses();

// Например редирект
return $httpResponses->redirect('http://phpixie.com/');

// Задаем хедеры и код ответа
return $httpResponses->stringResponse('Not found', $headers = array(), 404);

// Создаем загрузку файла
return $httpResponses->downloadFile('pixie.jpg', 'image/png', $filePath);

// Загрузка файла из строки
// Например для CSV
return $httpResponses->download('report.csv', 'text/csv', $contents);


Шаблоны


Шаблонизатор PHPixie поддерживает наследование шаблонов, блоки и возможность легко добавлять свои расширения и даже форматы.
По умолчанию стандартный бандл подгружает шаблоны из папки bundles/app/assets/templates.
В ней уже лежат два файла которые использовались стандартным процессором Greet.

Начнем с того что создадим простой шаблон:

<!-- bundles/app/assets/templates/quickstart/message.php -->

<!--
Функция $_() кодирует текст как HTML, чтобы символы
'<', '>' и т.д. не ломали верстку, а так же защищает от
большинства XSS атак.
-->
<p><?=$_($message)?></p>


Теперь добавим в процессор еще одно действие:

// bundles/app/src/Project/App/HTTPProcessors/Quickstart.php

//...
    public function renderAction($request)
    {
        $template = $this->builder->components()->template();
        
        return $template->render(
            'app:quickstart/message',
            array(
                'message' => 'hello'
            )
        );
    }
//...
}


И видим результат по ссылке localhost/quickstart/render

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

$template = $this->components()->template();

$container = $template->get('app:quickstart/message');
$container->message = 'hello';
return $container->render();

// Или можно возвратить сам контейнер
// и он автоматически отрендерится
return $container;


Наследование шаблонов


Добавим базовый родительский шаблон

<!-- bundles/app/assets/templates/quickstart/layout.php -->

<h1>Quickstart</h1>

<div>
    <!-- Тут вставится контент дочернего шаблона -->
    <?=$this->childContent();?>
</div>


и используем его в нашем message.php

<!-- bundles/app/assets/templates/quickstart/message.php -->

<?php $this->layout('app:quickstart/layout');?>

<p><?=$_($message)?></p>


Теперь по адресу localhost/quickstart/render наш шаблон будет уже обернут в родительский. Кстати родительский шаблон в свою очередь тоже может иметь своих родителей.

Еще большей гибкости можно добиться используя блоки, которые позволяют заменять и добавлять контент в разные места родительского шаблона.

<!-- bundles/app/assets/templates/quickstart/layout.php -->

<!-- Опишем блок 'header' -->
<?php $this->startBlock('header'); ?>
    <h1>Quickstart</h1>
<?php $this->endBlock(); ?>

<!-- И выведем его -->
<?=$this->block('header') ?>

<div>
    <!-- Тут будет контент дочернего шаблона -->
    <?=$this->childContent();?>
</div>


Теперь добавим контент в этот блок с нашего message.php:
<!-- bundles/app/assets/templates/quickstart/message.php -->
<?php $this->layout('app:quickstart/layout');?>

<?php $this->startBlock('header'); ?>
    <h2>Message</h2>
<?php $this->endBlock(); ?>

<p><?=$_($message)?></p>


По умолчанию контент в блоке идет в порядке добавления. Это позволяет удобно организовать например подключение стилей и скриптов в зависимости от страницы. Но мы также можем сказать родительскому шаблону добавлять свой контент в блок только если дочерний не добавил его сам.

<!-- bundles/app/assets/templates/quickstart/layout.php -->

<?php $this->startBlock('header', true); ?>
    <h1>Quickstart</h1>
<?php $this->endBlock(); ?>

<!-- ... -->


Или например добавить контент в начало блока а не в конец:
$this->startBlock('header', false, true);


Подшаблоны


Включение подшаблона тоже возможно:

<?php include $this->resolve('some:template');?>


Генерация ссылок


Для генерации ссылок в шаблоне существуют два метода httpPath и httpUri:

<?php $url=$this->httpPath(
        'app.default',
        array(
            'processor' => 'hello',
            'action'    => 'greet'
        )
    );
    ?>
<a href="<?=$_($url)?>">Hello</a>


Базы данных и ORM



Соединения к базам данных описываются в глобальной конфигурации, вне бандлов. Например соединение к MySQL могло бы выглядеть вот так:

// assets/config/database.php

return array(
    'default' => array(
        'driver' => 'pdo',
        'connection' => 'mysql:host=localhost;dbname=quickstart',
        'user'     => 'pixie',
        'password' => 'pixie'
    )
);


PHPixie поддерживает не только реляционные базы данных, но и **MongoDB**.
Вы можете даже описать связи (one-to-one, one-to-many, many-to-many) между реляционными таблицами и коллекциями MongoDB и работать с ними используя тот же синтаксис. На данный момент никакой другой PHP ORM не поддерживает такого уровня интеграции.

Наполним базу данными. Например допустим мы хотим создать трекер для проектов, какие состоят из заданий. Схема могла бы выглядеть вот так:

CREATE TABLE `projects`(
    `id`         INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    `name`       VARCHAR(255),
    `tasksTotal` INT DEFAULT 0,
    `tasksDone`  INT DEFAULT 0
);

CREATE TABLE `tasks`(
    `id`        INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    `projectId` INT NOT NULL,
    `name`      VARCHAR(255),
    `isDone`    BOOLEAN DEFAULT 0
);

INSERT INTO `projects` VALUES
(1, 'Quickstart', 4, 3),
(2, 'Build a website', 3, 0);

INSERT INTO `tasks` VALUES
(1, 1, 'Installing', 1),
(2, 1, 'Routing', 1),
(3, 1, 'Templating', 1),
(4, 1, 'Database', 0),

(5, 2, 'Design', 0),
(6, 2, 'Develop', 0),
(7, 2, 'Deploy', 0);


И мы уже можем использовать ORM для доступа к этим данным. Добавим действие orm в процессор:

// bundles/app/src/Project/App/HTTPProcessors/Quickstart.php

//...
    public function ormAction(Request $request)
    {
        $orm = $this->builder->components->orm();
        
        $projects = $orm->query('project')->find();
        
        // Этот метод превратит сущности
        // в простые объекты.
        // Как уже говорилось, если
        // возвратить объекты, то они
        // перекодируются в JSON
        return $projects->asArray(true);
    }
//...


Теперь зайдя на localhost/quickstart/orm вы увидите JSON ответ с данными проектов.
Перед тем как глубже заглянуть в возможности новой ORM настроим связь один-ко-многим
между проектами и их заданиями.

// bundles/app/assets/config/orm.php

<?php

return array(
    'relationships' => array(
        array(
            'type'  => 'oneToMany',
            'owner' => 'project',
            'items' => 'task',
            
            // При удалении проектов
            // сразу удалять их задания
            'itemsOptions' => array(
                'onOwnerDelete' => 'delete'
            )
        )
    )
);


Сущности


Создание, изменение и удаление сущностей вполне интуитивно:

$orm = $this->builder->components->orm();

// Создание сущности через ее репозиторий
$projectRepository = $orm->repository('project');
$project = $projectRepository->create();

// или быстрый подход 
$project = $orm->createEntity('project');

// Изменение и сохранение
$project->name = 'Buy Groceries';
$project->save();

$task = $orm->createEntity('task');
$task->name = 'Milk';
$task->save();

// Добавление задания в проект
$project->tasks->add($task);

// Удаление проекта
$project->delete();

// Итерация по загрузчиках
$projects = $orm->query('project')->find();
foreach($projects as $project) {
    foreach($project->tasks() as $task) {
        //...
    }
}


Запросы


Вот только асть того что сейчас возможно с ORM запросами:

$orm = $this->builder->components->orm();

// Найти проект по имени
$orm->query('project')->where('name', 'Quickstart')->findOne();

// Найти по id
$orm->query('project')->in($id)->findOne();

// Найти по массиву id
$orm->query('project')->in($ids)->findOne();

// Добавление нескольких условий
$orm->query('project')
    ->where('tasksTotal', '>', 2)
    ->or('tasksDone', '<', 5)
    ->find();
    
// Группировка условий
//WHERE name = 'Quickstart' OR ( ... )
$orm->query('project')
    ->where('name', 'Quickstart')
    ->or(function($query) {
        $querty
            ->where('tasksTotal', '>', 2)
            ->or('tasksDone', '<', 5);
    })
    ->find();

// Альтернативный синтаксис
$orm->query('project')
    ->where('name', 'Quickstart')
    ->startWhereConditionGroup('or')
        ->where('tasksTotal', '>', 2)
        ->or('tasksDone', '<', 5)
    ->endGroup()
    ->find();
    
// Для сравнение полей в базе, добавьте '*' к оператору
// Найти проекты у которых tasksTotal = tasksDone
$orm->query('project')
    ->where('tasksTotal', '=*', 'tasksDone')
    ->find();

// Найти проекты с хотя бы одним заданием
$orm->query('project')
    ->relatedTo('task')
    ->find();
    
// Найти проект с конкретным заданием
$orm->query('project')
    ->where('tasks.name', 'Routing')
    ->findOne();

// Или так
$orm->query('project')
    ->orRelatedTo('task', function($query) {
        $query->where('name', 'Routing');
    })
    ->findOne();

// Подгрузить задания для проектов
// во время выборки (eager loading) 
$orm->query('project')->find(array('task'));

// Изменить данные во всех проектах
$orm->query('project')->update(array(
    'tasksDone' => 0
));

// Подсчитать законченные проекты
// и потом сделать выборку тем же запросом.
// Например для разбиения на страницы
$query = $orm->query('project')
    ->where('tasksTotal', '=*', 'tasksDone');
    
$count = $query->count();

$query
    ->limit(5)
    ->offset(0)
    ->find();


PHPixie ORM включает множество оптимизация для уменьшения количества запросов к базе.
Например можно связать несколько заданий к проекту одним запросом:

$orm = $this->builder->components->orm();

// Запрос проекта
$projectQuery = $orm->query('project')
    ->where('name', 'Quickstart');

// Запрос обозначающий первые 5 заданий
$tasksQuery = $orm->query('task')->limit(5);

// К этому времени к базе вызовов еще не было

// Теперь свяжем из вместе одним запросом
$projectQuery->tasks->add($tasksQuery);


Использование запросов вместо работы с сущностями резко уменьшает количество запросов. Это особенно заметно в ситуации со связями многие-ко-многим. Как и с поддержкой MongoDB, никакая другая ORM этого не позволяет.

Расширение сущностей


Скорее всего вы захотите расширить классы моделей для добавления функционала. В других имплементациях это приводит к тесной связи логики с самой ORM и базой данных. PHPixie же позволяет вам описать полностью независимые врапперы, которые декорируют сущности, и таким образом полностью независимы от ORM. Это означает что тесты писать гораздо легче и не понадобится никаких фиксаций или тестовых данных.

Вот простенький враппер:

// bundles/app/src/Project/App/ORMWrappers/Project.php;

namespace Project\App\ORMWrappers;

class Project extends \PHPixie\ORM\Wrappers\Type\Database\Entity
{
    // Добавим метод который скажет нам
    // закончен проект или нет
    public function isDone()
    {
        return $this->tasksDone === $this->tasksTotal;
    }
}


Теперь зарегистрируем его в бандле:

// bundles/app/src/Project/App/ORMWrappers.php;
namespace Project\App;

class ORMWrappers extends \PHPixie\ORM\Wrappers\Implementation
{
    // Прописываем имена моделей для которых есть врапперы
    protected $databaseEntities = array(
        'project'
    );
    
    // И метод который его строит
    public function projectEntity($entity)
    {
        return new ORMWrappers\Project($entity);
    }
}


Все, теперь можем пробовать:

//Find the first project
$project = $orm->query('project')->findOne();

//Check if it is done
$project->isDone();

Также можно объявить врапперы для Запросов и Репозиториев чтобы добавить или изменить из функционал. Например можно добавить к запросу метод добавляющий сразу несколько условий.

Есть больше!



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

Меня и так все устраивало !

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

  • Забудьте обо всем вне папки /bundles/app/
  • Поскольку бандлы вы использовать не будете, не создавайте ярлык в папке /web, а просто смело кидайте в нее ваши веб-файлы как делали во второй версии. Если вы потом передумаете и захотите работать с бандлами, просто перенесете файлы с /web в /bundles/app/web. А до этого времени не заморачивайтесь.
  • Чуть чуть расширив стандартные классы легко можете добиться поведения второй версии:


Делаем так чтобы процессоры строились автоматически, по неймспейсу, без надобности прописывать каждый в HTTPProcessor:
// /bundles/app/src/Project/App/HTTPProcessor.php

namespace Project\App;
class HTTPProcessor extends \PHPixie\DefaultBundle\Processor\HTTP\Builder
{
    protected $builder;
    protected $attribute = 'processor';
    
    public function __construct($builder)
    {
        $this->builder = $builder;
    }
    
    public function processor($name)
    {
        if(!array_key_exists($name, $this->processors)) {
            $class = '\Project\App\HTTPProcessors\\'.ucfirst($name);
            if(!class_exists($class)) {
                return null;
            }
            $this->processors[$name] = new $class($this->builder);
        }
        
        return $this->processors[$name];
    }
}


Добавляем методы before() и after(), как было в контроллерах. Как дополнительный бонус, если before() возвратит какой-то результат то уже не вызывать само действие:

// /bundles/app/src/Project/App/HTTPProcessors\Controller.php

namespace Project\App\HTTPProcessors;

asbtarct class Controller extends \PHPixie\DefaultBundle\Processor\HTTP\Actions
{
    protected $builder;
    protected $attribute = 'processor';
    
    public function __construct($builder)
    {
        $this->builder = $builder;
    }
    
   public function process($request)
   {
      $result = $this->before($request);
      if($result !== null) {
         return $result;
      }
      
      $result = parent::process($request);
      
      return $this->after($request, $result);
   }
   
   public function before($request) {
   
   }
   
   public function after($request, $result) {
      return $result;
   }

}


Конец



Теперь у вас должно быть все чтобы начать работать с PHPixie 3, но есть еще много не прочитанной документации, как на сайте, так и тут на хабре. У большинства компонентов уже есть своя документация на обновленном сайте или даже русская версия здесь. Все компоненты могут использоваться без фреймворка, покрыты тестами на 100%, и почти все имеют максимальную метрику качества на CodeClimate.

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

Бенчмарки



image

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


  1. Mirantus
    27.07.2015 19:09
    +1

    Большое спасибо за ваш труд. Очень приятно, что фреймворк развивается, видимо феи и вправду волшебные.


    1. jigpuzzled Автор
      27.07.2015 19:13

      Спасибки! Кстати у нас теперь есть наклейки: twitter.com/dracony_gimp/status/625388999809585153

      Попробую их как-то доставить к пользователям. В Германию могу почтой передать, в Украину в принципе тоже. А вот в Россию хз, дорого ведь =(
      Могу скинуть рисунком =)


  1. AreD
    27.07.2015 20:03
    +1

    А в каких, по вашему мнению, серьезных проектах ныне используется PHPixie? Кроме плюсов, могли бы привести минусы вашего фреймворка?


    1. jigpuzzled Автор
      27.07.2015 20:29
      +2

      Мне если честно никто не пишет письма «мы использовали пиксю здесь» (а жаль), но можете например поискать на Гитхабе среди опенсорсных. Точно знаю что я использовал в серьезных, но не могу ссылки дать по NDA. Кстати хорошая идея сделать шоукейс на сайте.

      Насчет минусов думаю самый важный в том что во второй версии не было бандлов, и как следствие код внутри коммюнити не распространялся. Для той же самой Ларавел можно с пакетов неплохой сайт собрать с минимум кода. Еще я бы сказал что порог входа все же выше чем в Ларавел, так как я старался делать «правильно» и в Пикси например нет статических «фасадов», как результат надо больше времени приделять архитектуре, параметрам конструктора итд. «По быстрому на коленке» конечно тоже можно, но не так как например в Слим. Если на Слиме можно даже без знаний ООП писать, то в Пиксе даже вот быстрый старт предполагает что пользователь понимает «чем абстрактный класс отличается от интерфейса».


      1. AreD
        27.07.2015 20:40
        +1

        Спасибо за развернутый ответ, не примите мой предидущий вопрос за троллинг, я давно наблюдаю за вашей разработкой, но меня не покидает ощущение что проект существует в первую очередь для себя, решения своих насущных проблем, развития в себе навыка программирования. Отасти из-за того, что нет примеров повсеместного применения другими большими компаниями, обсуждения программистами. На том же тостере всего 2 вопроса о PHPixie.


        1. jigpuzzled Автор
          27.07.2015 22:29
          +1

          Ну думаю компании отсеивались дизайном сильно. Насчет популярности то на самом деле все хорошо: Результаты опроса популярности PHP фреймворков от Sitepoint.


        1. Grox
          28.07.2015 03:50
          +3

          Мы делали проект на PHPixie v2. Ему уже почти 2 года. Всё это время продолжает развиваться.


  1. Rhaps107
    27.07.2015 20:18
    +1

    После перехода с xslt на twig- или php-шаблоны, очень хочется иметь в шаблонизаторе поиск, хотя бы близкий к функциональности xpath, чтобы ловко жонглировать данными и не думать о их красивой подготовке на стороне модели/контроллера. Есть что-то такое в вашем шаблонизаторе?


    1. jigpuzzled Автор
      27.07.2015 20:36

      Хм, тут может помочь библиотека используема в конфигах, то есть 'phpixie/slice':

      $data = $sliceComponent->arrayData($someArray);
      
      //Providing a default value
      $data->get('name', 'Trixie');
      
      //Throw an exception if 'name' is missing
      $data->getRequired('name');
      
      //Accessing a nested field
      $data->get('users.pixie.name');
      
      //You can also 'slice' the data to avoid long paths
      $pixie = $data->slice('users.pixie');
      $pixie->get('name');
      
      //Getting data as array
      $data->get();
      
      //Getting all set keys
      $data->keys();
      


      Например если вы делаете $data->get('users.pixie.name', 'defaultValue'); и какого-то ключа по дороге не встретилось, то получите назад свое 'defaultValue'. А с $data->slice('user'); удобно передавать параметр кусочек данных в подшаблон например. Таким образом подшаблону будет все равно на полный путь к сегменту 'user'. Но таких хитростей какие можно проедать в xpath к сожалению нет. Хотя можно сделать свое расширение для поиска, могу помочь =) Что например вам бы хотелось?


      1. Rhaps107
        24.08.2015 19:13

        С сильным запозданием я осознал, что хотелось бы анаг с синтаксисом xpath, но для поиска по php-массиву. Наиболее приближенный найденный функционал — jsonpath -http://goessner.net/articles/JsonPath/. Ведь массивы php и json в принципе идентичные типы хранения данных (однозначно представляют друг друга). Костылем можно было бы использовать сам jsonpath, в аргументы ему передавая json_encode от данных шаблона.


  1. dom1n1k
    27.07.2015 21:06
    +3

    И логотип приличный сделали


    1. jigpuzzled Автор
      27.07.2015 21:07

      Спасибо =) Может теперь будет не стыдно показать клиенту =)))


  1. AlexLeonov
    28.07.2015 00:30

    Респект за труд. Пусть будет больше фреймворков, хороших и разных!

    Но. Я не могу обойтись без «но». Не сочтите за занудство.

    array(...)

    Если вы до сих пор поддерживаете 5.3 — для меня это повод закрыть страничку проекта и никогда больше не открывать. Скажите — я ошибаюсь и вы ориентируетесь на свежие версии PHP?

    //$_GET['name']
    $request->query()->get('name');

    //$_POST['name']
    $request->data()->get('name');

    Не понял? Один и тот же метод? И как различать данные из GET и POST? Или есть метод ->post('name')? Но это же ужасно!

    $projects = $orm->query('project')->find();

    Тоже непонятно. Если «find» — тогда "$project". Если уж "$projects" — тогда наверное должно быть «findAll». Как различаются методы, возвращающие одну запись и коллекцию записей? И есть ли вообще коллекции?

    'owner' => 'project',
    'items' => 'task',

    Почему не Project::class или Task::class? Как сёрфить по таким строковым литералам в IDE?


    1. jigpuzzled Автор
      28.07.2015 01:59
      +2

      Все полностью работает на 5.3 — 7 + hhvm. А что в этом собственно плохого? Вам никто не мешает использовать фишки той версии какой вам нравится, пишите '[]' сколько хотите. Я просто не видел причины урезать аудиторию ради короткого синтаксис массивов в коде.

      И как различать данные из GET и POST?


      Вы наверное не заметили:

      $request->query()->get('name'); и $request->data()->get('name');. query() -> $_GET, data() -> параметры из тела запроса ( например $_POST, но также и если это JSON запрос то тоже будет распарсено).

      Почему не Project::class или Task::class?


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

      find() -> коллекция, а findOne() -> одна сущность


      1. AlexLeonov
        28.07.2015 11:25

        Вы наверное не заметили:

        Да, не заметил, извините.
        Но всё равно имена выбраны чудовищно несемантично. Почему я должен помнить, что query — это get, а data — это post или, внезапно, распарсенный JSON, причем это методы?


        1. jigpuzzled Автор
          28.07.2015 11:51
          +1

          Ну например «Query» параметры $_GET называет и PSR-7 стандарт и HTTP RFC.

          $_POST они называют Parsed Body, но так как это длинно слишком я заменил на data.

          Проблема в том что ПХП неверно назвала эти массивы именами HTTP методов. Теперь когда все больше упора делается на REST это сильно заметно. Например делая HTTP PUT запрос, как то странно плучать данные с $_POST. Куда более странно при POST запросе читать что-то с $_GET.

          Я мог бы назвать get() и post(), но это давно не стандарт, взгляньте например на Symfony2.


  1. vshemarov
    28.07.2015 02:03
    -1

    Все компоненты могут использоваться без фреймворка

    Это точно? Я как-то решил заюзать библиотеку Image из версии 2, где тоже декларировалось, что компоненты могут без фреймфорка использоваться. Но оказалось, что без ядра (класса \PHPixie\Pixie) это хозяйство не работает.



  1. jigpuzzled Автор
    28.07.2015 02:16

    Во второй версии в ядре была либа чтения конфигов, это все что было нужно этой библиотеке. То есть вы могли бы запустить Image в любом другом фреймворке, просто передав ему в параметр «new \PHPixie\Pixie». Совсем не обязательно было это ядро «запускать» методом handle_http_request(). Но мне \то почти сразу не нравилось, поэтому у третьей версии ядра нет =)

    Вот например первый раздел в доках по ОРМ как раз о том как запустить без фреймворка: phpixie.com/components/orm.html

    Этот коммент — ответ к habrahabr.ru/post/263551/#comment_8516543, случайно нажал не ту кнопку


    1. vshemarov
      28.07.2015 02:31

      Да я понял, зачем там нужно было ядро, но т.к. тянуть ради этого код ядра не хотелось, то сварганил небольшой костылик, который притворялся ядром и отдавал либе конфиги. Мне интересно было, как это в новой версии реализовано (как раз в сторону ORM смотрю). Похоже, сейчас можно будет без костылей обойтись :)


      1. jigpuzzled Автор
        28.07.2015 02:44

        Все довольно просто, во второй версии \PHPixie\Pixie также являлся DI контейнером в котором лежали все компоненты. Сейчас же компоненты принимаю зависимости строго через конструктор и не зависят совсем от контейнера. В примере с ОРМ, вы передаете ему инстанс компонента базы данных, настройки и класс со врапперами. То есть о никаком фреймворке он не знает в принцыпе и кто построил ему зависимости ему тоже не интересно =)


  1. noway
    28.07.2015 09:41

    Несколько месяцев назад искал микрофреймворк для небольшого проекта и в итоге остановился на PHPixie. Старт с ним мне показался самым быстрым. Но на тот момент 3-я ветка была в активных коммитах, а 2-ая видимо уже вне поддержки. Решил не эксперементировать и оставить до лучших времен. При возможности попробую еще раз!


    1. jigpuzzled Автор
      28.07.2015 11:53

      Вторая ветка не вне поддрежки, в нее просто не добавляются новые фичи, но баги фиксятся будут. Можете смело пробовать третью =)


  1. andrewiWD
    28.07.2015 10:39

    В целом выглядит симпатично. Кроме шаблонизатора. Хотя конечно в нём важнее быстродействие. Есть только пару вопросиков:

    — ORM поддерживает ли вложенные запросы, транзакции? (после Phalcon, о наболевшем)
    — Чем обусловлен выбор наименований в $request для работы с $_POST, $_GET?
    query() — понятно,
    data() — почему не post?
    attributes() — почему не parameters()? В комментариях вы сами написали «параметры»…


    1. Fesor
      28.07.2015 11:44

      data() — почему не post?

      Наверное потому что data бывает не только у post запросов, это может быть результат парсинга тела PUT запросов. В symfony/http-foundation оно именуется request вообще.

      attributes() — почему не parameters()?

      Наверное потому что это все же атрибуты а не параметры. Параметры приходят от пользователя (и по сути это query), атрибуты могут добавлять мидлвэры, парам конвертеры всякие и т.д.


    1. jigpuzzled Автор
      28.07.2015 11:58
      +1

      Транзакции есть конечно, и даже вложенные с сейвпойнтами: github.com/phpixie/database#transactions

      Вложенные запросы это больше по части базы данных а не ОРМ, они тоже тут: github.com/phpixie/database#tables-subqueries-and-joins

      А чем вам не понравился шаблонизатор?


      1. Fesor
        28.07.2015 13:21

        А почему ваш ORM не ORM а QueryBuilder?


        1. jigpuzzled Автор
          28.07.2015 13:59
          +1

          Ммм? Я просто ссылку скинул на модуль базы данных, ОРМ в другом репозитории =)


          1. Fesor
            28.07.2015 15:00

            Я про

            $projects = $orm->query('project')->find();
            


            Это как бы не ORM, это QueryBuilder. Возможно QueryBuilder + мэппер на объекты. Но я так понимаю что если я сделаю что-то типа этого:

            $a = $orm->query('project')->find(1);
            $b = $orm->query('project')->find(1);
            
            assert($a === $b, 'ORM должен это разруливать);
            


            то так оно работать не будет.


            1. jigpuzzled Автор
              28.07.2015 15:33
              +1

              Эээ, это где сказано то что ОРМ такое должен разруливать? Такое разруливают только ОРМ в которых есть ентити менеджер (который жрет память потому что должен хранить все инстансы сущностей в памяти), в ПХП насколько я знаю только доктрина так делает.

              ОРМ = мапит данные к объектам и мапит связи типа один-ко-многим. QueryBuilder это если просто использовать Database компонент, там нет сущностей, просто строки с БД, нет связей и т.д.


            1. Invision70
              29.07.2015 02:34
              -1

              Работать не будет, согласен, вы одинарную кавычку пропустили.


  1. danaki
    31.07.2015 17:17

    2015, Карл


    1. Invision70
      31.07.2015 18:24
      +1

      Как же за#%ал этот мем


      1. danaki
        03.08.2015 19:34

        Ceep Calm and Carry On — этот мем не за#%ал?


  1. greabock
    31.07.2015 21:54
    +1

    Автор — очень занимательный персонаж. Он постоянно висит в чате русского сообщества Laravel и неустанно повторяет транслитом:

    routing v vashem laravel govno
    arhitectura vashego laravel govno


    и так далее… ну взрослый же человек…
    Да хоть десять раз говно. Выйди ты из чата и не мучайся. То ли зависть к большому коммьюнити душит, то ли еще какие причины — мне не ведомо. Но подобное поведение для человека, который пытается продвинуть свой продукт, и по сути является лицом своего продукта — как минимум, некрасиво.


    1. Fesor
      31.07.2015 22:45
      +1

      Как бы я не любил Laravel (роутинг там к слову норм ибо симфони) — но поделка автора похуже будет.


    1. jigpuzzled Автор
      31.07.2015 23:24
      -1

      Я с вами там ибо у вас самый живой русский РНР чат. В последнее время толкаю об архитектуре только когда меня спрашивают + с аргументами, а то если почитать ваш коммент, то я получается сижу и троллинг копи-пасчу =)