Pull to refresh

Comments 59

P.S. один из комментариев из реддита: «I loved PHPixie, but I cannot be taken serious on my workspace using a framework named pixie with that colorful website =/»

Печально когда фреймворк на работе оценивают по цветовой гамме =(
Он говорит не про качество фреймворка, а про неудачный несерьезный брендинг с феями и мультяшностью. Названия и образы, с которыми работает программист — важная ведь штука.
UFO just landed and posted this here
Почему «нет»? Я не исключаю, что некоторым это нравится. Но автору комментария — нет, ему это мешает воспринимать серьезно свою работу. Мне тоже не нравится брендинг такого типа, это дело вкуса. С продуктом должно быть приятно работать, иначе приходится переступать через себя.
Схемка достаточно универсальная для большинства PHP фреймворков
И да и нет. Если например применить ее к скажем Симфони то схема будет очень поверхностная, так как в симфони кроме этого на каждой стадии еще много всего делается. Для пикси же ( в Core которой только горстка классов) кроме того что на схемке мало что происходит.
// Теперь мы можем использовать $pixie->singleton в любом месте,
// и всегда получить тот же объект. В качестве дополнительного бонуса
// объект будет создан только тогда когда он будет нужен

Singleton — это когда класс сам контролирует, чтобы не существовало более одного его экземпляра, нет? У Вас получается Service Locator + Factory, что мало чем отличается от globals/static.
namespace App;
class Pixie extends \PHPixie\Pixie {
    protected $modules = array(
        'db' => '\PHPixie\DB',
        'orm' => '\PHPixie\ORM'
    );
}

// Теперь мы можем использовать $pixie->db и $pixie->orm 



Каким образом DB попадает внутрь ORM?
Никаким =) ORM доступается к DB через $pixie->db. А вот $pixie передается каждому из модулей в вконструктор
Т.е. внутри класса ORM нет никаких гарантий, что $pixie->db существует и это именно DB а не stdClass?

$db = new DB($config);

function foo() {
    global $db;
}

Не напоминает?
Да, в этом идея (и одна из проблем) DI контейнеров. Правда стоить отметить что $pixie->db сам по себе никуда не пропадет, то есть если вы написали код и он там был то он там и будет =)

А то что это может быть stdClass это фича а не баг, если его можно подменить на что-нибудь то протестировать ОРМ будет гораздо проще так как вместо DB я просто вставлю его мок
Да, в этом идея (и одна из проблем) DI контейнеров.

О каких проблемах речь? Берем Pimple:

class Config {}

class DB {
    public function __construct(Config $config) { ... }
}

class ORM {
    public function __construct(DB $db) { ... }
}

$pimple['config'] = $pimple->share(function ($pimple) {
    return new Config;
});

$pimple['db'] = $pimple->share(function ($pimple) {
    return new DB($pimple['config']);
});

$pimple['orm'] = $pimple->share(function ($pimple) {
    return new ORM($pimple['db']);
});

$orm = $pimple['orm'];
$orm->doSomething();

Когда я получаю $orm, я полностью уверен, что туда пришли все нужные зависимости и мне не нужно ничего проверять. Все зависимости как на ладони. В Вашем случае гарантий никаких и мне нужно постоянно проверять, «а есть ли вообще такой объект?», «а это точно экземпляр нужного класса?» и т.д.
И где тут гарантия? $pimple['db'] тоже может вернуть вам stdClass, если конечно вы скажете ему вернуть его. Так же и $pixie, например $pixie->orm->result() построит вам объект со всеми зависимостями.

Может я плохо объяснил но объекты модулей строятся во время bootstrap(), то есть если вы прописали там модуль то он точно там будет и точно не будет каким-то stdClass

Я не понимаю чем хуже писать так:
        public function build_demo(){
                  return new Demo($this->dependency1(), $this->dependency2())
        }
       protected $demo;
       public function demo() {
              if($this->demo == null)
                     $this->demo = $this->build_demo();
              return $this->demo;
       }
       public function a($param) {
              return new A($this->dependency1(), $this->dependency2(), $param);
       }

чем так например:
        $pimple['demo'] =$c->share(function ($c) {
                 return new Demo($c['dependency1'], $c['dependency2']);
        });
        $pimple['a'] = function($param) {
                return new A($c['dependency1'], $c['dependency2'],$param);
        }


Экономит несколько строк конечно, но приимущиства на лицо:
1) в варианте с пикси IDE будет показывать подсказки по результатам функций. А вот что такое $c['demo'] IDE точно знать не будет
2) все описано как атрибуты класса не возня с магическим массивом
3) код с Пимл фактически недокументируем, так как в phpDocumentor насколько я помню нет возможности описать ключи массива.

Для простоты в \App\Pixie есть $instance_classes которые создаются только раз при первом запросе и которым в конструктор передается $pixie.
И где тут гарантия? $pimple['db'] тоже может вернуть вам stdClass, если конечно вы скажете ему вернуть его.

Тут:

class ORM {
    public function __construct(DB $db) { ... }
}

Глядя на класс ORM, я понимаю что в него должно прийти. А глядя на код, который создает экземпляр — мне ясно что туда ушло:

return new ORM($pimple['db']);

Теперь Ваше:

namespace App;
class Pixie extends \PHPixie\Pixie {
    protected $modules = array(
        'db' => '\PHPixie\DB', // можно это удалить? оно же не связано с ORM, правда?
        'orm' => '\PHPixie\ORM'
    );
}

// Теперь мы можем использовать $pixie->db и $pixie->orm 

Я смотрю на вызывающий код и не пойму, что с чем связано.
Ну ладно, думаю я, и лезу смотреть внутрь ORM. А там меня ждет:

Никаким =) ORM доступается к DB через $pixie->db. А вот $pixie передается каждому из модулей в вконструктор

class ORM {
    public function foo() {
        return $pixie->db->bar();
    }
}

WTF? Что такое db? Откуда оно тут взялось?
Мне долго надо по исходникам скакать, чтобы понять что откуда берется? Ни изнутри, ни снаружи не понятно что с чем связано.

Явное лучше, чем неявное.
Класс ОРМ это «плагин» к контейнеру, то есть он сам по себе является контейнером и просто добавляет фактори методы. то, явно или нет передавать параметры в конструктор зависит уже от самой функции которую вы напишете для своего класса.

Насчет того откуда взялось $pixie->db. Искать придется только в СВОИХ исходниках, то есть в классе App\Pixie где разработчик сам подключает модули и добавляет атрибуты. вам не надо будет лезть в код самого фреймворка.

Конечно если не нравится использовать массив $modules всегда можно подключить модуль явно в конструкторе.
namespace App;
class Pixie extends \PHPixie\Pixie {
    public $db;
    public function after_bootstrap() {
         $this->db = new \PHPixie\DB($this);
    }
}


Как я писал, все что в App неймспейсе это только пример, можно писать как больше нравится)
Ваши зависимости так и остаются неявными.

$this->db = new \PHPixie\DB($this);

Откуда Вы знаете, что в данный момент аргумент $this содержит необходимые зависимости для DB?

Это мне напоминает:

$sum = sum($_GLOBALS);

Угадаете, сумму чего вычисляет этот код?
Главное отличие в том что его можно подменить в одном месте, в то время как вызов статика включает имя класса и поэтому подменять класс надо в каждом файле где его упоминают
'db' => '\PHPixie\DB'


Глядя на эту запись и не заглядывая в недра DB, Вы можете сказать от чего зависит этот класс? Какие данные нужны, чтобы создать его экземпляр?

Главное отличие в том что его можно подменить в одном месте, в то время как вызов статика включает имя класса и поэтому подменять класс надо в каждом файле где его упоминают

Вы предлагаете что-то вроде:

$class = 'foo';
$class::bar();

$class = 'baz';
$class::bar();
Сам \PHPixie\DB тоже просто контейнер, он создает экземпляры классов которые работают с базой. Модулям в конструктор передается только сама $pixie

Пример класса модуля: github.com/dracony/PHPixie-ORM/blob/master/classes/PHPixie/ORM.php

По сути да, это то что вы написали, только теперь вам надо еще создать маcсив таких $class чтобы держать их в одном месте и у вас получится свой контейнер =)

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

Автор фреймворка показывает пробелы в элементарных знаниях ООП, что очень сказывается на архитектуре и общем качестве кода. Что само по себе было бы простительно, если бы автор не показывал категоричную уверенность в верности своих знаний и соответственно характерное «все тупые, один я умный». Для справки: www.reddit.com/r/PHP/comments/1ka8bx/symfony2_being_oop_only_on_the_surface/
Хех, перед кем я извиняюсь — автор поста и есть автор фреймворка. Тут даже распинаться нет необходимости, политика продвижения продукта говорит все за себя.

Чтобы не было сомнений:
* www.reddit.com/user/dracony
* habrahabr.ru/users/jigpuzzled/
* dou.ua/forums/topic/6733/
* www.linkedin.com/pub/roman-tsiupa/39/65/a55
* habrahabr.ru/post/183010/#comment_6359478

Особенно выделяется:
* Работает в той же фирме
* Схожий стиль, особенно тенденция использовать "=)"
* Активное продвижение и углубленные знания фреймворка, на который якобы случайно натолкнулся
* Активное продвижение других разработок юзера dracony
* На реддите dracony выставляет ту же диаграмму
* Попался на другом ресурсе с такой же «политикой продвижения»
А как Вам такой ход:
О нем я услышал совсем недавно, его упомянул в своем твите Phil Sturgeon (разработчик PyroCMS и член PHP-FIG) ...

habrahabr.ru/post/178833/

А вот и сами упоминания:
The PHP community has learned so many valuable lessons about FW dev over the last 2 years, yet PHPixie ignores them all...
...This whole class is littered with untestable code.
This is a bunch of static shit in PHPixie which is totally untestable, because of the statics.

PHPixie site:https://twitter.com/philsturgeon/
Ммм это он о старой версии, в новой нет статиков и она полностью покрыта тестами. С ним есть еще несколько холиваров на реддите, при чем некоторые из его идей таки были имплементированы.

Имхо сам Фил немного слишком вспыльчив, как минимум раз в неделю он кого-то бранит на своем блоге, то разработчиков самого ПХП, то программистов с кривыми руками итд
Но других упоминаний нет. Получается, что в качестве первых строк упоминания продвигаемого Вами фреймворка Вы использовали ссылку на человека (с указанием его регалий), который нелестно отзывается о Вашем фреймворке. И это выглядит как попытка приобщиться к чему-то большему любыми средствами.
Простите, но в нём нет положительной оценки.
О нем я услышал совсем недавно, его упомянул в своем твите Phil Sturgeon (разработчик PyroCMS и член PHP-FIG)

Я где-то сказал что-то об оценке? Просто мне понравилась фраза Кохана Lite
Ой, а как так получилось, что твит от 24 апреля, а Ваш пост forums.laravel.io/viewtopic.php?id=4305 от 3 января? Т.е. явно не из твитера Вы о нём узанли…
Не понимаю чем так лучше ларавел, в нем куча плохих решений в красивой обертке, кстати вот мой пост на их форуме:
forums.laravel.io/viewtopic.php?id=6140

Они даже признали что их eager loading не так уж хорош и хотели исправить его в L4, но видимо руки как всегда не дошли. Если уж и писать на фреймворке который «строился на компонентах симфони» то я уже б выбрал симфони
Причина делать IN в возможности подменить часть запроса чем-то ещё. Например, вынести индексы в Sphinx не заменяя весь код или зашардить записи или сделать драйвер для AR под Mongo или Redis. Ну и запросы действительно не такие тяжёлые выходят.

А за выборку в 1000 записей на запрос в веб-приложениях надо отрывать руки.
Мммм а почему отрывать руки?
Просто не надо грузить все 1000 записей сразу в память а читать курсором и проблем не будет. А если я например хочу написать скрипт экспорта что мне делать? Для самой базы данных вернуть 1000 строк совсем не проблема

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

1. Ставим в очередь задачу.
2. Задача выбирает 100 записей и если ещё остались — ставит себя в очередь повторно.

Всё это происходит, естественно, в фоне.

Курсоры — штука отличная, но есть особенности вроде этой или этой. Импорт может длится долго и держать курсор открытым как-то непрактично.
А то что обе особенности запосчены 7 лет назад это ничего? может мускул немного апдейтнулся за 7 лет?
Первая — да, баг и да, старый, но MySQL 5.0 ещё не такая редкость. Стоит учитывать, что на фреймворке будут делаться не только проекты, но и продукты, для которых совместимость с shared-хостингами важна.

Вторая — это ограничение Oracle. Стоило всё-таки не постить ссылку на трекер MySQL, а сослаться на что-то более независимое.
Стоит учитывать, что на фреймворке будут делаться не только проекты, но и продукты, для которых совместимость с shared-хостингами важна.

То есть как коммунизм, пусть всем будет одинаково плохо. Тогда сразу давайте и замыканий в пхп не использовать, так как на хостингах еще и 5.2 может ПХП стоять.
5.3 на хостингах встречается достаточно часто, в отличие от свежего MySQL. Проблема, конечно, для многих незначительна, но как автору фреймворка, которому важно, чтобы фреймворк использовали, важна. Можно сделать фреймворк, который будет работать на 5.5, но на данный момент он мало кому будет нужен.
А можно пример хостинга где 5.0?
ну и них и сайт кагбе сам намекает на качество хостинга, верстка слезла и тд
Это не отменяет, того печального факта, что такими хостингами пользуются и пользуются очень многие.
Так перестали бы?
Может есть смысл тогда сделать опрос? Я конечно понимаю что те кто ставит вордпрес могут купит такой хостинг, но нормальный программер, если он уже и фреймворк выучил купит ли такой?
При выборе фреймворка под конкретный хостинг, тот фреймворк, который не работает, просто выкинут из числа выбираемых. Не всегда программист покупает хостинг. Иногда его ставят перед фактом. Не все работают на фрилансе. Например, в огромных конторах вроде Siemens IE6 перестали пользоваться на 3—4 года позже, чем в общем по больнице потому как обновить на таком количестве машин просто дорого.
Но все же вы делаете ставку на PHP 5.3 ( именно такой использует Ларавел). Относительно свежий PHP и старый старый MySQL странная комбинация
5.3 уже не сильно свежий (2009) и больше не поддерживается.

Мы (Yii) также делаем ставку на 5.3 в версии 2.0, а в текущей 1.1 поддерживаем 5.1+ и вот почему:

1. Статистика по версиям PHP 5. Тут видно, что всё, что выше 5.3 пока широко не используется, а также что 5.2 начинает уходить. К тому времени как Yii 2.0 релизнется, PHP 5.3 будет наиболее распространён.
2. 5.4, в отличие от 5.3 не даёт ничего сверхполезного для фреймворка.

По MySQL, к сожалению, я такой статистики не находил, но часто встречал проекты на 5.0 и 5.1. Кстати, новые версии ветки 5.0 выходили до 2012, а 5.1 и сейчас активно поддерживается: en.wikipedia.org/wiki/MySQL#Versions
Кстати а почему не сделать тогда на джойне типа так:

Взяли авторов
Select id,name from authors where name like '%jig%'

Взяли посты
Select posts.id, posts.title from posts INNER JOIN authors on posts.author_id = authors.id where authors.name like '%jig%'

Зачем ходить по массиву авторов, собирать айдишки, потом делать из них список, который потом мускулу передавать длиннейшим стрингом (плохо для дебага потом), который ему потом еще и парсить.
А что делать если у поля праймари ки состоит из двух полей? ИН тогда не сработает
На более-менее нагруженном проекте запрос с LIKE может серьёзно попортить жизнь. Для этой задачи подходит Sphinx или SOLR, на вход которого подаётся поисковый запрос, а получается кучка ID-шников. Эти самые ID используются в запросе с IN к обычной реляционной базе.

Используя JOIN мы лишаем себя возможности перетащить часть запроса в специализированное хранилище или вынести на отдельный сервер.

Мы, когда в Yii2 переходили с JOIN на IN, делали много тестов и поняли, что производительность отличается не сильно и не всегда в пользу JOIN.
Нет ну я не спорю что есть кейсы когда ИН может быть оправдан, но думаю много проектов как вы сами сказали бывают на шерд хостингах где таких няшек нет.

К слову вот я сделал бенчмарк и протестировал поиск по LIKE и по полю с индексом, результат (время исполнения 10000 запросов в секундах):

LIKE test
IN: 35
JOIN: 36

Test Indexed field
IN: 34
JOIN: 29

JOIN работает быстрее на полях с ключами, так как не надо тратить время на собирания массива с айдишками. Кроме того он более читабелен и такие запросы легче мониторить. Я конечно понимаю что Сфинкс и тд не смогут работать с джойном, но я не понимаю почему ради них должны страдать те кто их не используют. Лучше бы вынести их в отдельную библиотеку.

Кроме того джойн будет использовать курсор а не жрать память попусту.

Код бенчмарка:
<?php $db = new PDO('mysql:host=localhost;dbname=benchmark', 'root'); $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); function benchmark($callback){ $t = time(); for ($i = 0; $i < 10000; $i++) $callback(); return time() - $t; } function test_in($db, $query) { return function() use($db, $query) { $ids = []; $posts = []; foreach($db->query("SELECT * from posts where $query ") as $post) { $posts[] = $post; $ids[] = $post['author_id']; } $authors = $db->query("SELECT * from authors where id in (".implode(',', $ids).")")->fetchAll(); if (count($authors) != 168) die; }; } function test_join($db, $query) { return function() use($db, $query) { $posts = $db->query("SELECT * from posts where $query ")->fetchAll(); $authors = $db->query("SELECT a.id, a.name from authors a JOIN posts p ON p.author_id = a.id where p.$query")->fetchAll(); }; } $in_time = benchmark(test_in($db, "title like '%puzzle%'")); $join_time = benchmark(test_join($db, "title like '%puzzle%'")); echo(" LIKE test IN: $in_time JOIN: $join_time "); $in_time = benchmark(test_in($db, "published=1")); $join_time = benchmark(test_join($db, "published=1")); echo(" Test Indexed field IN: $in_time JOIN: $join_time ");

Код бенчмарка плохо отформатировался в комменте он тут gist.github.com/dracony/6699327

PHPixie использует только 1 запрос чтоб сразу взять и пост и автора. даже с оверхедом фреймворка получатся так:

LIKE test 22
Test Indexed field 20

Фактически 40% экономии
1. Подсчёт времени исполнения неточный. Сделали бы хотя-бы microtime.
2. Тест не совсем честный. Для IN выбирается больше данных, чем для JOIN.
3. Вы не ограничили выборку, то есть результат сильно зависит от количества выбираемых данных. Стоит поставить на тот же post LIMIT 100 хотя-бы.
3. Вы не смотрите на абсолютные цифры. Уже на сотне записей и тот и тот метод даёт затраты в районе секунды ±. Больше записей за один раз выбирать не стоит. Импорт — отдельный случай.
4. При учтённых пунктах 2 и 3 IN ощутимо быстрее.
Хм а почему данных выбирается больше? И там и там у меня получилось 168 авторов и постов, не так уж много.

Микротайм на виндовсе плохо работает, потому я взял time() но прогнал каждый подход 10000 раз ( то есть ошибка на время одного исполнения равна 1/10000 секунды)

То есть пункты учтены. Можно взглянуть на ваш тест?
а на comment_count был индекс?
очень странно в случае второго теста, по логике в LIKE задержка должа бы быть больше чем при простом сравнении.

Кстати я добавил в тест подход от пхпикси, вот апдейт кода:
gist.github.com/dracony/6701279
Да, естественно индекс был.

Погонял свежий тест. Результат тот же: IN быстрее или на уровне двух запросов, один из которых JOIN. В один запрос с JOIN (который eager), естественно, на малых объёмах данных, быстрее. Но стоит взглянуть и на абсолютные циферки для извлечения сотни записей:

Like test:
IN: 0.031533 s
JOIN: 0.024357 s
EAGER: 0.006185 s

Indexed test:
IN: 0.005537 s
JOIN: 0.005744 s
EAGER: 0.001047 s



Like сразу выкидываем потому как делать такое средствами MySQL не стоит (разве что для блога подойдёт, но там разница заметна не будет). Остаётся Indexed, где разницы между IN и JOIN фактически нет, а отставание от EAGER ничтожно.
Ну тот у кого старый мускул сфинкса там тоже не имеет. А отставание от eager в 5 раз не так уж ничтожно.

Кстати это как раз IN не сильно быстрее JOIN, так зачем тогда тем кто без сфинкса и тд делать IN? если с джойном читабельнее и нет проблем если больше 1000 строк?
Зачем делать `IN` я уже рассказал. Если проект вдруг чудом выстрелит, придётся подтаскивать и Sphinx и всё остальное. А вот вставлять палки в колёса, исключая такую возможность только ради того, чтобы «читать SQL» — странно. Максимум, что делают с SQL, если работает корректно — EXPLAIN. Про более 1000 строк в одном запросе я уже говорил. Так делать не стоит.
...я взял time() но прогнал каждый подход 10000 раз (


А как так получлось, что эти тесты находятся в чужом репозитории? Или всё же в Вашем?
В его, в его. На даты посмотрите и не паранойте по мелочам.
Sign up to leave a comment.

Articles