Как работает PHPixie — Жизнь одного запроса, контейнер и парадигма

    image
    Я уже много раз писал о фреймворке PHPixie и программировании на нем. В этот раз мы заглянем внутрь и посмотрим жизненный цикл приложения, к счастью простота и линейность кода позволяют сделать это с относительной легкостью.

    Как и например Symfony, PHPixie состоит из двух частей: библиотеки компонентов и базового проекта, правда в случае PHPixie базовый проект более тонкий и состоит всего из нескольких файлов. Он здесь исполняет роль примера и поэтому изменение его под себя не только приветствуется но в некоторых случаях даже необходимо. Именно для этого важно понимать что и как происходит в системе. Используя свои несколько ограниченные умения в области рисования я подготовил диаграмму обработки запроса


    PHPixie

    Конечно тем кто уже знаком с MVC (или даже VC в этом случае, так как модели я не нарисовал) наверняка эта схема уже покажется знакомой, но для новичков может быть очень полезна. Итак начнем c index.php куда и попадают все запросы, здесь самые важные строчки это:
    $pixie = new \App\Pixie();
    $pixie->bootstrap($root)->handle_http_request();
    


    И сразу же мы попадаем на самую важную часть, класс App\Pixie который является сердцем фреймворка, его DI контейнером. Через него можно получить доступ ко всем другим компонентам. App\Pixie наследует от PHPixie\Pixie из библиотеки PHPixie-Core. Базовый проект оглашает этот класс вместо использования PHPixie\Pixie напрямую для предоставления разработчику возможности внести в него свои изменения ( например подключить модуль).

    Сразу стоит отметить что добавлять новые сущности в этот контейнер на ходу, как например в Silex, нельзя, все надо описывать явно в классе. Хотя это и может показаться не таким удобным на первый взгляд, но зато позволяет добиться лучшей читабельности кода, полностью продокументировать все сущности (так как все они становятся атрибутами класса) а также получить подсказки по этим сущностям в IDE. Поскольку PHPixie\Pixie содержит также все фактори методы, то это позволят нам с легкостью заменить любой класс фреймворка на свой путем перегрузки соответствующего метода.

    Метод bootstrap() инициализирует $pixie, считывает конфигурацию, подключает обработку исключений итд. Как раз в handle_http_request() проходит обработка запроса. Этот процесс состоит из трех этапов:
    • Создание объекта $request класса PHPixie\Request
    • Этот объект передается в соответствующий контроллер и выполняется соответствующий action
    • В процессе исполнения екшена контроллер изменяет объект $response ( PHPixie\Response )
    • Данные из $response (хедеры и контент) отсылаются пользователю


    Все три самых важных объекта $request, $response и $pixie доступны как атрибуты класса PHPixie\Controller. Теперь отвлечемся немного на еще несколько парадигм написания кода на PHPixie:

    Не использовать оператор «new» нигде кроме фактори методов. Каждый новый класс должен иметь фактори метод (например в App\Pixie) для создания его екземпляров. Такой подход позволяет легко заменить один класс другим, что особенно важно при написании юнит тестов. Так тестируя например контроллер вы теперь сможете передать в него замоканный App\Pixie который вместо реальных классов передаст их моки.

    Не использовать статические проперти и методы. Использование статики сильно усложняет написание тестов. Используя PHPixie можно легко обойтись без них, достаточно добавить экземпляр как атрибут App\Pixie и вы сможете получить к нему доступ практически из любого места. Таким образом мы фактически получим синглтон. Кстати сделать это можно еще путем добавления его в $instance_classes.

    namespace App;
    class Pixie extends \PHPixie\Pixie {
        public function __construct() {
               $this->instance_classes['singleton'] = '\App\Singleton';
        }	
    }
    
    // Теперь мы можем использовать $pixie->singleton в любом месте,
    // и всегда получить тот же объект. В качестве дополнительного бонуса
    // объект будет создан только тогда когда он будет нужен
    


    Как работают модули

    Каждый модуль для PHPixie это дополнительная библиотека классов которая предоставляет свой DI контейнер очень похожий на главный PHPixe\Pixie, то есть он состоит из методов фабрик для создания экземпляров классов который входят в модуль. Потом мы просто добавляем его в массив модулей в главный контейнер:

    namespace App;
    class Pixie extends \PHPixie\Pixie {
    	protected $modules = array(
    		'db' => '\PHPixie\DB',
    		'orm' => '\PHPixie\ORM'
    	);
    }
    
    // Теперь мы можем использовать $pixie->db и $pixie->orm 
    


    А что делать если я например хочу подменить класс PHPixie\ORM\Model на свой App\Model? Все просто, надо еще сделать свой App\ORM (extends PHPixie\ORM ) метод get() которого вместо модели PHPixie\ORM\Model будет возвращать ту что нужна нам. в этом еще больше проявляется одна из идей фреймворка — как можно больше использовать стандартные приемы ООП вместо каких-то магий. Например чтобы подменить класс самого фреймворка приходится применять subclass_prefix и делать єто на уровне конфигурации а не собственно программирования. Такой подход позволяет намного улучшыть понимание системы, так как по большей части в флове можно разобраться не зная ничего о фреймворке, просто посмотрев на сами классы.

    А как же хуки, ивенты и прочее?

    Их нет и я так понимаю не будет. Такие вещи полностью из другой парадигмы так как делают код нелинейным, особенно это относится к ивентам, где всегда не до конца понятно который из листенеров запустится первым и что случится если он сам вызовет какой-то ивент. А также от их использования очень часто страдает читабельность бектрейсов, так как они вызываются самим фреймворком где-то там где сам программист кода точно не писал. Если нужно что-то сделать в каком-то месте гораздо проще перегрузить метод который как-раз это и делает и дописать к нему ту логику что нужно.

    В следующей статье я либо более подробно рассмотрю как PHPixie работает с базами данных, либо более расширенно расскажу в чем плюсы и минусы линейного против event driven программирования.
    Поделиться публикацией
    Похожие публикации
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

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

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

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



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

                $db = new DB($config);
                
                function foo() {
                    global $db;
                }
                

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

                  А то что это может быть stdClass это фича а не баг, если его можно подменить на что-нибудь то протестировать ОРМ будет гораздо проще так как вместо DB я просто вставлю его мок
                  • 0
                    Да, в этом идея (и одна из проблем) 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, я полностью уверен, что туда пришли все нужные зависимости и мне не нужно ничего проверять. Все зависимости как на ладони. В Вашем случае гарантий никаких и мне нужно постоянно проверять, «а есть ли вообще такой объект?», «а это точно экземпляр нужного класса?» и т.д.
                    • 0
                      И где тут гарантия? $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.
                      • 0
                        И где тут гарантия? $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? Откуда оно тут взялось?
                        Мне долго надо по исходникам скакать, чтобы понять что откуда берется? Ни изнутри, ни снаружи не понятно что с чем связано.

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

                          Насчет того откуда взялось $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 неймспейсе это только пример, можно писать как больше нравится)
                          • +1
                            Ваши зависимости так и остаются неявными.

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

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

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

                            $sum = sum($_GLOBALS);
                            

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


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

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

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

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

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

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

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

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

                Чтобы не было сомнений:
                * 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 выставляет ту же диаграмму
                * Попался на другом ресурсе с такой же «политикой продвижения»
                • +2
                  А как Вам такой ход:
                  О нем я услышал совсем недавно, его упомянул в своем твите 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/
                  • 0
                    Ммм это он о старой версии, в новой нет статиков и она полностью покрыта тестами. С ним есть еще несколько холиваров на реддите, при чем некоторые из его идей таки были имплементированы.

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

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

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

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

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

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

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

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

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

                              То есть как коммунизм, пусть всем будет одинаково плохо. Тогда сразу давайте и замыканий в пхп не использовать, так как на хостингах еще и 5.2 может ПХП стоять.
                              • 0
                                5.3 на хостингах встречается достаточно часто, в отличие от свежего MySQL. Проблема, конечно, для многих незначительна, но как автору фреймворка, которому важно, чтобы фреймворк использовали, важна. Можно сделать фреймворк, который будет работать на 5.5, но на данный момент он мало кому будет нужен.
                                • 0
                                  А можно пример хостинга где 5.0?
                                  • 0
                                    Agava, например. А вообще во всём виноват Wordpress, который на 5.0 себя замечательно чувствует.
                                    • 0
                                      ну и них и сайт кагбе сам намекает на качество хостинга, верстка слезла и тд
                                      • 0
                                        Это не отменяет, того печального факта, что такими хостингами пользуются и пользуются очень многие.
                                        • 0
                                          Так перестали бы?
                                          Может есть смысл тогда сделать опрос? Я конечно понимаю что те кто ставит вордпрес могут купит такой хостинг, но нормальный программер, если он уже и фреймворк выучил купит ли такой?
                                          • 0
                                            При выборе фреймворка под конкретный хостинг, тот фреймворк, который не работает, просто выкинут из числа выбираемых. Не всегда программист покупает хостинг. Иногда его ставят перед фактом. Не все работают на фрилансе. Например, в огромных конторах вроде Siemens IE6 перестали пользоваться на 3—4 года позже, чем в общем по больнице потому как обновить на таком количестве машин просто дорого.
                                            • 0
                                              Но все же вы делаете ставку на PHP 5.3 ( именно такой использует Ларавел). Относительно свежий PHP и старый старый MySQL странная комбинация
                                              • 0
                                                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
                          • 0
                            Кстати а почему не сделать тогда на джойне типа так:

                            Взяли авторов
                            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%'

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

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

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

                                К слову вот я сделал бенчмарк и протестировал поиск по 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 ");

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

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

                                  LIKE test 22
                                  Test Indexed field 20

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

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

                                      То есть пункты учтены. Можно взглянуть на ваш тест?
                                      • 0
                                        В одном случае вы делаете SELECT * from authors, а в другом SELECT a.id, a.name from authors a.

                                        Мой вот: gist.github.com/samdark/6700234
                                        • 0
                                          а на comment_count был индекс?
                                          очень странно в случае второго теста, по логике в LIKE задержка должа бы быть больше чем при простом сравнении.

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

                                            Погонял свежий тест. Результат тот же: 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 ничтожно.
                                            • 0
                                              Ну тот у кого старый мускул сфинкса там тоже не имеет. А отставание от eager в 5 раз не так уж ничтожно.

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


                                          А как так получлось, что эти тесты находятся в чужом репозитории? Или всё же в Вашем?
                  • 0
                    Этот коммент можно удалить, я случайно не туда запостил

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