«О нет!», воскликнет читатель, утомлённый разными мини-микро-слим-фреймворками и QueryBuilder-ами и будет прав.
Нет ничего скучнее, чем очередной фреймворк на PHP. Разве что «принципиально новая» CMS или новый дейтинг.
Так зачем же я с упорством, достойным лучшего применения, шагаю по неудобным подводным камням и выставляю на потеху публике суд товарищей своё творение? Заранее зная, что гнев критиков, как мощное цунами обрушится на этот пост и похоронит его на самом днище Хабра?
Не знаю. Как не знал в своё время Колумб, зачем он отплывает от уютных берегов Испании. Надеялся ли он найти путь в Индию? Конечно да. Но не знал точно — доплывёт ли?
Видимо и у программистов на PHP, к которым я вот уже 13 лет себя причисляю, есть такая же внутренняя потребность — выставлять свой код и зажмуривать глаза, ожидая реакции коллег.
Что вас ждет под катом?
- Открытый исходный код, лицензия LGPL
- Код, полностью совместимый с PHP 7.0-7.2
- 100% покрытие юнит-тестами
- Библиотеки, проверенные временем в реальных проектах (и только проклятая прокрастинация мешала мне опубликовать их ранее!)
Ну и, разумеется, история изобретения очередного велосипеда на костыльном приводе фреймворка*!
* вообще говоря это пока еще не фреймворк, а просто набор библиотек, фреймворком он станет чуть позже
- Фирменное наименование: Runn Me!
- Вендор: Runn
- GitHub: github.com/RunnMe
- Composer: packagist.org/packages/runn
Немного истории или Откуда взялась идея «написать еще один фреймворк?»
Да, собственно говоря, ниоткуда. Она всегда была.
Разные интересные проекты, в которых довелось поучаствовать за время программистской карьеры, клали в личную копилку стандартные решения для стандартных же задач — так у меня, как и у любого нормального программиста, скапливалась своя библиотека функций, классов и библиотечек.
Лет шесть назад руководство компании, в которой я тогда работал, поставило задачу: разработать свой собственный фреймворк. Сделать легковесный MVC-каркас, взяв только самое необходимое, добавить к нему специфичные библиотеки предметной области (поверьте — очень специфичные!) и собрать некое универсальное решение. Решение, надо отметить, получилось, но специфичность предметной области не позволила ему стать массовым — код не публиковался, продавались инсталляции на площадку клиента. А жаль. Некоторые вещи действительно опережали своё время: достаточно сказать, что пусть примитивное, но всё-таки довольно похожее подобие composer мы с командой сделали тогда совершенно самостоятельно и немного раньше, чем появился, собственно стабильный публичный composer 🙂
Благодаря этому опыту мне довелось изучить практически все существовавшие тогда в экосистеме PHP фреймворки. Попутно произошло еще одно событие, очередной «переход количества в качество» — я стал преподавать программирование. Сначала в одной известной онлайн-школе, потом сосредоточился на развитии своего собственного сервиса. Стал «обрастать» методиками преподавания, учебными материалами и, конечно же, студентами. В этой тусовке и возникла идея некоего «учебного фреймворка», намеренно упрощенного для понимания начинающими, но при этом позволяющего всё-таки успешно разрабатывать несложные веб-приложения в соответствии с современными стандартами и тенденциями.
Три года назад, как реализация этой идеи «учебного фреймворка», родился небольшой MVC-фреймворк под названием «T4»*. В названии нет ничего особенного, просто сокращение от «Технологический макет, версия 4». Думаю понятно, что предыдущие три версии вышли неудачными и только с четвертой попытки нам, вместе с тогдашними моими студентами, удалось создать что-то действительно интересное.
* позже я узнал, что так в третьем рейхе называлась программа по стерилизации и умерщвлению неизлечимо больных людей… конечно же сразу встал вопрос о смене названия
T4 благополучно развивался и рос, стал известным, как говорится, «в узких кругах» (очень узких), на нём был сделан ряд довольно крупных проектов, но росло и внутреннее недовольство этим решением.
В начале этого года я окончательно созрел для переформатирования накопившегося кода. Вместе с группой единомышленников, которые тоже активно использовали T4, мы приняли ряд базовых принципов построения нового фреймворка:
- Делаем его слабосвязанным набором библиотек, так, чтобы каждую либу можно было подключить и использовать отдельно.
- Стараемся сохранять здоровый минимализм там, где это возможно
- Сам каркас для веб- и консольных приложений — тоже одна из библиотек, тем самым мы избегаем монолитности.
- Стараемся не изобретать велосипеды и максимально сохраняем те подходы и тот код, которые уже зарекомендовали себя в T4.
- Отказываемся от поддержки устаревших версий PHP, пишем код под самую актуальную версию.
- Стараемся делать код максимально гибким. Если можно — вместо классов и наследования используем интерфейсы, трейты и композицию кода, оставляя пользователям фреймворка возможность заменить эталонную реализацию любого компонента своей.
- Покрываем код тестами, добиваясь 100% покрытия.
Так родился проект, который сначала назвали «Running.FM», а потом окончательно уже переименовали в «Runn Me!»
Именно его я сегодня и представляю.
Кстати, слово «runn» сконструировано искусственно: с одной стороны чтобы быть понятным всем и вызывать ассоциации с «run», с другой — чтобы не совпадало ни с одним из словарных слов. Мне вообще нравится буквосочетание «run»: я еще в RunCMS в своё время успел поучаствовать 🙂
В данный момент проект «Runn Me!» находится в середине пути — какие-то библиотеки уже можно применять в продакшне, какие-то в процессе переноса из старых проектов и рефакторинга, а какие-то мы еще не начали переносить.
В начале было Core
Уместить в один пост рассказ о каждой библиотеке проекта «Runn Me!» невозможно: их много, хочется подробно поведать о каждой, ну и к тому же это живой проект, в котором всё изменяется к лучшему буквально ежедневно 🙂
Поэтому я решил разбить рассказ о проекте на несколько постов. В сегодняшнем пойдет речь о базовой библиотеке, которая называется «Core».
- Назначение: реализация базовых классов фреймворка
- GitHub: github.com/RunnMe/Core
- Composer: github.com/RunnMe/Core
- Установка: командой composer require runn/core
- Версии: как и в любой другой библиотеке проекта «Runn Me!», поддерживаются три версии, соответствующие предыдущей, актуальной и будущей версиям PHP:
7.0.*, 7.1.* и 7.2.*
Массив? Объект? Или всё вместе?
Благодатная идея объекта, состоящего из произвольных свойств, которые можно создавать и удалять «на лету», как элементы в массиве, приходит в голову каждому программисту на PHP. И каждый второй эту идею реализует. Не стали исключением и я с моей командой: ваше знакомство с библиотекой Runn\Core я хочу начать с рассказа о концепции ObjectAsArray.
Делай раз: определи интерфейс, который позволит тебе кастить твой объект к массиву и обратно: массив превращать в объект, не забыв в этом интерфейсе пару полезных методов (merge() для слияния объекта с внешними данными и рекурсивный кастинг к массиву)
github.com/RunnMe/Core/blob/master/src/Core/ArrayCastingInterface.php
namespace Runn\Core; interface ArrayCastingInterface { public function fromArray(iterable $data); public function merge(iterable $data); public function toArray(): array; public function toArrayRecursive(): array; }
Делай два: собери мегаинтерфейс, который опишет поведение будущего объекта-как-массива максимально полно, заложив туда максимум полезного: сериализацию, итерацию, подсчет числа элементов, получение списка ключей и значений, поиск элемента в этом «объекте-массиве».
github.com/RunnMe/Core/blob/master/src/Core/ObjectAsArrayInterface.php
namespace Runn\Core; interface ObjectAsArrayInterface extends \ArrayAccess, \Countable, \Iterator, ArrayCastingInterface, HasInnerCastingInterface, \Serializable, \JsonSerializable { ... }
Делай три: напиши трейт, который станет эталонной реализацией мегаинтерфейса. См. github.com/RunnMe/Core/blob/master/src/Core/ObjectAsArrayTrait.php
В результате мы получили полноценную реализацию «объекта-как-массива». Использование интерфейса ObjectAsArrayInterface и трейта ObjectAsArrayTrait позволяет делать нам примерно так:
class someObjAsArray implements \Runn\Core\ObjectAsArrayInterface { use \Runn\Core\ObjectAsArrayTrait; } $obj = (new someObjAsArray)->fromArray([1 => 'foo', 2 => 'bar']); $obj[] = 'baz'; $obj[4] = 'bla'; assert(4 === count($obj)); assert([1 => 'foo', 2 => 'bar', 3 => 'baz', 4 => 'bla'] === $obj->values()); foreach ($obj as $key => $val) { // ... } assert('{"1":"foo","2":"bar","3":"baz","4":"bla"}' === json_encode($obj));
Кроме базовых возможностей в ObjectAsArrayTrait реализована возможность перехвата присваивания и чтения «элементов объекта-массива» с помощью кастомных сеттеров-геттеров, этакий задел для будущих классов:
class customObjAsArray implements \Runn\Core\ObjectAsArrayInterface { use \Runn\Core\ObjectAsArrayTrait; protected function getFoo() { return 42; } protected function setBar($value) { echo $value; } } $obj = new customObjAsArray; assert(42 === $obj['foo']); $obj['bar'] = 13; // выводит 13, присваивания не происходит
Важно: null is set!
Да, элемент объекта-массива, чье значение null, считается определенным.
Это решение вызвало немало споров, но всё-таки было принято. Поверьте, на то есть серьезные причины, о которых будет рассказано дальше, в повествовании о библиотеке ORM:
class someObjAsArray implements \Runn\Core\ObjectAsArrayInterface { use \Runn\Core\ObjectAsArrayTrait; } $obj = new someObjAsArray; assert(false === isset($obj['foo'])); assert(null === $obj['foo']); $obj['foo'] = null; assert(true === isset($obj['foo'])); assert(null === $obj['foo']);
И зачем это всё?
Ну как же! Всё, о чем я рассказывал выше — это только начало. От интерфейса \Runn\Core\ObjectAsArrayInterface наследуются другие интерфейсы и имплементируют классы, дающие жизнь двум «веткам классов»: Collection и Std.
Коллекции
Коллекции в Runn Me! — это объекты-массивы, снабженные большим количеством дополнительных полезных методов:
namespace Runn\Core; interface CollectionInterface extends ObjectAsArrayInterface { public function add($value); public function prepend($value); public function append($value); public function slice(int $offset, int $length = null); public function first(); public function last(); public function existsElementByAttributes(iterable $attributes); public function findAllByAttributes(iterable $attributes); public function findByAttributes(iterable $attributes); public function asort(); public function ksort(); public function uasort(callable $callback); public function uksort(callable $callback); public function natsort(); public function natcasesort(); public function sort(callable $callback); public function reverse(); public function map(callable $callback); public function filter(callable $callback); public function reduce($start, callable $callback); public function collect($what); public function group($by); public function __call(string $method, array $params = []); }
Разумеется, сразу же в распоряжении разработчика имеется как эталонная реализация этого интерфейса в виде трейта CollectionTrait, так и готовый к использованию (или наследованию) класс \Runn\Core\Collection, добавляющий к реализации методов из трейта удобный конструктор.
С использованием коллекций становится возможным писать примерно такой код:
$collection = new Collection([1 => 'foo', 2 => 'bar', 3 => 'baz']); $collection->prepend('bla'); $collection ->reverse() ->map(function ($x) { return $x . '!'; }) ->group(function ($x) { return substr($x, 0, 1); }); /* получится что-то вроде [ 'b' => new Collection([0 => 'baz!', 1 => 'bar!', 2 => 'bla!']), 'f' => new Collection([0 => 'foo!'), ), ] */
Что важно знать о коллекциях?
- Большинство методов не изменяют исходную коллекцию, а возвращают новую.
- Большинство методов не гарантирует сохранение ключей элементов.
- Наилучшее применение коллекций — хранение в них множеств однородных или подобных объектов.
Типизированные коллекции
Кроме «обычных» коллекций в библиотеку Runn\Core включен интересный инструмент, позволяющий полностью контролировать объекты, которые могут содержаться в коллекции. Это типизированные коллекции.
Всё очень и очень просто:
class UsersCollection extends \Runn\Core\TypedCollection { public static function getType() { return User::class; // тут может быть и название скалярного типа, кстати } } $collection = new UsersCollection; $collection[] = 42; // Exception: Typed collection type mismatch $collection->prepend(new stdClass); // Exception: Typed collection type mismatch $collection->append(new User); // Success!
Std
Вторая «ветка» кода, в чём-то противоположная коллекциям, называется «Стандартный объект». Строится он также пошагово:
Делай раз: определи интерфейс для «магии».
namespace Runn\Core; interface StdGetSetInterface { public function __isset($key); public function __unset($key); public function __get($key); public function __set($key, $val); }
Делай два: добавь ему стандартную реализацию (см. github.com/RunnMe/Core/blob/master/src/Core/StdGetSetTrait.php )
Делай три: собери из «запчастей» класс, опирающийся на StdGetSteInterface с множеством дополнительных возможностей.
github.com/RunnMe/Core/blob/master/src/Core/Std.php
В конце пути мы получаем с вами достаточно универсальный класс, пригодный для решения множества задач. Вот лишь несколько примеров:
$obj = new Std(['foo' => 42, 'bar' => 'bla-bla', 'baz' => [1, 2, 3]]); assert(3 === count($obj)); assert(42 === $obj->foo); assert(42 === $obj['foo']); assert(Std::class == get_class($obj->baz)); assert([1, 2, 3] === $obj->baz->values()); // о, да, реализация вот этой штуки весьма монструозна: $obj = new Std; $obj->foo->bar = 42; assert(Std::class === get_class($obj->foo)); assert(42 === $obj->foo->bar);
Разумеется, «умения» класса Std не исчерпываются chaining-ом, доступом к свойствам, как к элементам массива и наоборот, кастингом к самому классу. Он умеет гораздо больше: валидировать и очищать данные, отслеживать обязательные к заполнению свойства и т.д. Но об этом позже, в других статьях цикла.
А дальше?
Всё только начинается! Впереди нас ждут рассказы о:
- Мультиисключениях
- Валидаторах и санитайзерах
- О хранилищах, сериализаторах и конфигах
- О реализации Value Objects и Entities
- Об HTML и представлении форм на стороне сервера
- О собственной библиотеке DBAL, включая, конечно же, QueryBuilder!
- Библиотека ORM
- и как финал — MVC-каркас
Но это всё в будущих статьях. А пока что с праздником, товарищи! Мир, труд, код! 🙂

P.S. Детального плана со сроками у нас нет, как нет и желания успеть к какой-то очередной дате. Поэтому не спрашивайте «когда». По мере готовности отдельных библиотек будут выходить статьи о них.
P.P.S. С благодарностью приму сведения об ошибках или опечатках в личные сообщения.
©
КДПВ (с) Mart Virkus 2016
Картинка в заключении статьи из гуглопоиска картинок
ссылка на оригинал статьи https://habrahabr.ru/post/326960/

Добавить комментарий