smpl / mydi Goto Github PK
View Code? Open in Web Editor NEWmoved to https://github.com/cekta/di
moved to https://github.com/cekta/di
Сделать загрузку из xml файла конфигурации контейнеров
формат описать после реализации и сделать xsd для валидации и небольших подсказка в ide
Заметил прикольное поведение в php вот примеры кода
Упрощенный пример
Подобная штука возникает в mydi в таком случае:
$locator->a = function () use ($locator) {
$obj->test = $locator->b;
};
$locator->b = function () use ($locator) {
$obj->test = $locator->a; // Вот тут Notice возникает при первом вызове b
};
$locator->a;
а вот теперь нету Notice и ведет себя как должно
$locator->a = function () use ($locator) {
$obj->test = $locator->b;
};
$locator->b = function () use ($locator) {
$obj->test = $locator->a;
};
$locator->resolve('a'); // Вот тут разрешение зависимости начинает не с магии
Было бы не плохо чтобы на основные действия производилось логирование с помощью PSR-3
Изначально эта возможность вводилась как легкий способ добавить auto complete в ide
пример:
/**
* @property \PDO pdo
**/
class App extends Locator {
}
$app = new App;
$app->pdo->query('SELECT * FROM users'); // тут будет автокомплит всех методов pdo
Сейчас проблему автокомплита параметров можно решить через DynamicReturnTypePlugin
и работать напрямую с методами $locator->resolve('pdo')->query('SELECT * FROM users') и тут автокомплит сработает.
Также вторая причина это проблема которую я не могу решить #13 и мало кто может понять это поведение
собственно стоит подумать и возможно отказаться от переопределения методов _-get и __set и поправить документацию
В composer.json использовать только стабильные зависимости
возможность установки LoaderInterface[]
возможность получение ранее установленных загрузчиков
Ситуация когда А зависит от Б, а Б в свою очередь зависит от А и они вызывают друг друга до бесконечности, например:
$locator->a = function () use ($locator) {
$obj = new stdClass();
$obj->test = $locator->b;
return $obj;
};
$locator->b = function () use ($locator) {
$obj = new stdClass();
$obj->test = $locator->a;
return $obj;
};
$a = $locator->a; // Бесконечное разрешение зависимостей
было бы не плохо отлавливать такую ситуацию и сообщать о неверной конфигурации контейнеров A и Б
$locator['test'] = null;
$locator->isExist('test'); // вернет false хотя должно вернуть true
в методе isExist заменить isset на array_key_exists
Реализовать загрузку файлов из php файлов примерный формат файла
return 75;
Или сложная зависимсоть
use smpl\mydi\Container\Factory;
return new Factory(function () { return new stdClass;});
где имя файла это название контейнера а содержимое это его определение, загрузка определений должна происходить частично
В показать в Readme о наличие контейнера Lazy (краткое описание) и ссылку на задачу которая его сподвигла сделать #9
в методе resolve при разрешение зависимости имя которой не определено, в сообщение выводить имя этой зависимости sprintf('name is already exist, $s', $name) заменить на sprintf('name is already exist, %s', $name)
Иногда для построение зависимости нужны другие завимсимости и их построение это некая логика.
Например для подключения к бд надо знать адрес, логин, пароль, имя базы, которые будут определены в других местах, причем подключение к бд должно быть строго в единственном экземпляре и создаваться единожды, а потом возвращаться уже готовый объект.
Иногда необходимо каждый раз заново создавать объект (что то вроде фабричного метода)
Сделать возможность создавать разные типы контейнеров для разрешения зависимости.
Минимум:
Логика разрешения зависимости будет в виде обыкновенной анонимной функции если для разрешения зависимости необходимы другие контейнеры пусть через замыкание получает LocatorInterface и через него разрешает остальные зависимости.
Для контейнеров создадим простой ContainerInterface и его будем передавать LocatorInterface в качестве типов контейнеров, а реализация будет разной для каждого типа.
При этом в LocatorInterface если будет передаваться анонимная функция, будем считать что требуется Service
Для этого в документации не будет кода, а будут ссылки на файлы расположенные в tests/resource/ там будут лежать примеры кода разбитые по файлам, которые будут тестироваться функциональными тестами.
в composer.json можно определить команду отвечающую за запуск теста, сделать её
примеры и другие прикольные штуки смотри тут http://habrahabr.ru/post/247519/
в методе isExist и соответсвенно в isset($locator['containerName']) можно получить ответ false в случае если зависимость определена но ещё пока не загруженна, что является неверным, надо проверять может ли она быть загружена через Loader и если может то возвращать true но не загружать её пока не запросят
var_dump(20 === $locator->testContext); // false
Сделать простой способ конфигурации в php файлах как в случае с json
один Loader по загрузке простых типов, другой по загрузке контейнров
В качестве основной документации использовать тесты, указать это в readme
Для контейнеров и для Locator сделать ссылки
Некоторые люди хотели бы для описания зависимостей более простой и декларативный синтаксис.
Например загрузку параметров(в виде ключ значение) из различных форматов #35 ну и поддержку форматов json и php
Загрузку контейнеров и построение зависимостей (где элементы это это уже другие контейнеры)
в директории tests существующие тесты перенести в tests/unit и добавить директорию /tests/functional (здесь зависимости не будут мокаться) оно нужно для общего тестирование библиотеки и чтобы тестировать примеры кода из документации #41
Было бы не плохо чтобы LocatorInterface можно было бы работать как с массивом и он сохранял контейнеры, для этого надо реализвовать ArrayAccess
например:
$locator = new Locator();
$locator['test'] = 123;
$locator['magic'] = function () use ($locator) {
$result = new stdClass();
$result->param = $locator['test']; // 123
return $result;
};
$result = $locator['magic'];
echo $result->param; // 123
$result === $locator['magic']; // true
Также возможность ставить свойства объекта и получать к ним данные по ним (переопределив магические методы __get, __set)
пример:
$locator = new Locator();
$locator->test = 123;
$locator->magic = function () use ($locator) {
$result = new stdClass();
$result->param = $locator->test; // 123
return $result;
};
$result = $locator->magic;
echo $result->param; // 123
$result === $locator->magic; // true
ну и возможность смешанной работы
При использование Locator в большой системе необходимо в начале объявить все контейнеры и потом вызывать все необходимое зависимости, на объявление контейнера создается \Closure объект с анонимной функцией что занимает память, было бы не плохо создавать только те контейнеры которые необходимы и больше ничего не объявлять.
Для загрузки таких контейнеров нужен новый LoaderInterace
interface LoaderInterface {
/**
* Проверяет может ли загрузить данный контейнер этот Loader
*/
public function isLoadable($containerName);
/**
* Загрузка контейнера
*/
public function load($containerName);
}
с помощью LoaderInterface можно сделать загрузку определения контейнеров из php файлов или json или yaml или любого другова формата
и соответственно надо иметь возможность получать и устанавливать эти LoaderInterface в Locator и при resolve контейнера если он еще не определен попытаться его загрузить через Loader который сможет его загрузить
На текущий момент тесты для Locator объединен ООП стиль, ArrayAccess и доступ к элементам через __get __set объединены в одном юнит тесте.
Разделить на разные юнит тесты, и подготовить документацию в соответствие с #12
Убрать composer selfupdate
убрать опцию --dev из секции install она используется по умолчанию
В ситуации когда в контейнер добавляется анонимная функция, считать что это сервис, потому что сервисы чаще всего встречаются и чтобы не писать лишний текст а писать сокращенно.
Пример:
$locator->test = new Service(function () {
return 1;
}); // Старый вариант описания
$locator->magic = function () {
return 1;
}; // Должно быть равно сильно преведущему описанию.
в примерах использования указан некоректный пример
// Просто анонимная функция которая автоматически обернется в Factory, я так предпочитаю создавать
$locator->add(pdo2, function () use ($locator) {
return new \PDO($locator->add(dsn), $locator->add(user), $locator->add(password));
});
перенести её в раздел сервисов
ещё несколько неточностей в примерах кода
Сделать описание для данного интерфейса, за что он отвечает.
Добавить возможность для добавления(add) зависимости.
Добавить возможность для разрешения(resolve) зависимости.
Отказ от поддержки 5.4 чтобы использовать новые возможности
библиотеку возможно будет использовать на 5.4 но работоспособность не гарантируется и не будет проверяться
code coverage в scrutinizer-ci не правильно отоброжается
да и наследование в тестах не лучшая практика наверно
Использовать
composer https://getcomposer.org/
Добавить пакет в менеджер composer https://packagist.org/
Использовать TDD для тестирования подгрузить phpunit с помощью composer
Для автопрогона тестов и CI использовать https://travis-ci.com/
Использование use для передачи Locator у функций не очень красиво и возможно не очень правильно.
Гораздо лучше будет если у ContainerInterface в методе resolve появиться аргумент LocatorInterface а как его использовать пусть решает сам контейнер (например передает первым аргументом в вызываемую функцию), вариант с использованием use останется доступным но считать его использование bad practice и только для обратной совместимости
Надо лишь подумать как быть с Lazy контейнером.
В src/loader/File.php не все методы протестированны (покрыть их все тестами) ото покрытие 98%.
В тестирование различных LoaderInterface иногда создаются файлы конфигурации и по окончанию теста удаляются
Вынести эти файлы в resource пусть будут статичными и не удаляются
Было бы хорошо иметь возможность построения дерева зависимостей чтобы потом проанализировать их или как то визуализировать.
Для этого в LocatorInterface необходимо добавить метод который будет возвращать ответ в виде:
[
'containerName' => [
'dependceContainer1',
'dependceContainer2',
],
'dependceContainer1' => ['dependceContainer3'],
'dependceContainer2' => [],
'dependceContainer3' => [],
];
Логика работы будет следующая вначале он у Locator запрашивает список всех валидных имен контейнеров (объявленных и тех которые можно подгрузить используя Loader) после этого он подгружает каждый контейнер и смотрит какие зависимости он затребовал для своей загрузки у Locator (там есть подобный механизм для защиты от зацикливаний, надо сделать его сохранение).
Добавить метод по получению всех зависимостей в LocatorInterface (объявленных и которые можно подгрузить)
Добавить метод в LoaderInterface который вернет список всех имен контейнеров которые он сможет подгрузить, он будет вызываться для списка контейнеров в LocatorInterface
Хотелось бы иметь возможность использовать отложенную инициализацию
Также иногда хотелось бы иметь анонимную функцию чтобы создавать однотипные объекты которые отличаются каким либо одним параметром, на текущий момент это можно сделать так.
$locator->magic = new Service(function () use ($locator) {
return function ($param) use ($locator) {
// тут какая то логика по созданию объекта учитывая параметр
$obj = new stdClass();
$obj->db = $locator->db;
$obj->param = $param;
return $obj;
}});
// вызываем с разными параметрами
$locator->magic(1);
$locator->magic(2);
Хотелось бы убрать вложенность функций в объявление чтобы смотрелось проще и понятней, но логика была такой же, тогда можно применить Lazy
$locator->magic = new Lazy(function ($param) use ($locator) {
// тут какая то логика по созданию объекта учитывая параметр
$obj = new stdClass();
$obj->db = $locator->db;
$obj->param = $param;
return $obj;
});
// вызываем с разными параметрами
$locator->magic(1);
$locator->magic(2);
Нужды в LazyService как таковой нету, потому что там есть спорные моменты в поведение и они будут крайне редко востребованны и это можно будет реализовать используя готовые компоненты:
$locator->service = function() {
// Тут логика по созданию объекта может быть любой и огромное количество конфигурации
return new stdClass();
}
$locator->lazyService = new Lazy (function () use ($locator) {
return $locator->service;
})
$locator->resolve('lazyService');
Сейчас в ParserInterface есть единственный метод parse который в зависимости от аргумента с адресом до файла, разбирает его, по сути этот метод вообще можно сделать статичным он не от чего не зависит, а в smpl\mydi\loader\KeyValue в конструктор мы передаем $fileName который используем только для парсинга, возможно стоит $fileName хранить внутри ParserInterface и метод parse вызывать уже без аргументов, а в KeyValue уже путь до файла не передавать.
Написать LoaderInterace который будет читать файлы различных форматов и хранить данные в виде ключ значения в том виде в котором они были заданы
Для поддрежки разных форматов надо сделать возможность парсинга отдельной (ParserInterface) и для примера сделать json
Также можно перенести тесты в директорию tests/unit и соответсвенно подправить namespace
Раньше было удобно превращать анонимные функции в Service #11 потому что так почти всегда они и применялись.
После реализации #22 оборачивать их внутри LocatorInterface не очень удобно иногда удобней чтобы они возвращали анонимные функции которые можно было бы вызывать, а в случае необходимости трансформацию объектов можно сделать в LoaderInterface
Я думаю надо сделать отдельный интерфейс MutationInterface в котором можно будет определить какие объекты и как преобразовывать, его передавать в LoaderInterface который будет автоматически проверять и если необходимо преобразовывать, по умолчанию никаких преобразований не проводить.
После того как phpstorm будет адекватно работать с phpunit 4.x обновиться на него.
Собственно проблема в том что ServicLocator Loader не может реализовать метод getAllLoadableName для этого надо знать имена всех классов в проекте (чтобы потом провереять реализует ли он интерфейс LocatorAwareInterace через class_implements), а чтобы получить имена всех классов в проекте надо все файлы классов подключить (обычно используется composer class loader который подгружает нужные файлы по требованию) и уже потом вызвать get_declared_classes
Вот поэтому мы не можем получить список всех доступных контейнеров, как вариант можно кэшировать имена контейнеров( #50 ) и получать список кэшированных или просто возвращать пустой массив (сделаю пока так)
Имея парсеры к json и php можно используя их сделать новый Loader для построение сложных объектов на основе массива (в нем указывать зависимости в конструктор и так далее), параметры конфигурации хранить в старом KeyValue #35 а использовать их уже тут
примерный формат в php:
return [
MyClass::class => [
"construct" => [\PDO::class, Another::class]
],
Another::class => [], // Без параметров будет создаваться new Another()
\PDO::class => [
"construct" => ['dsn', 'username', 'password']
]
];
Собственно параметры 'dsn', 'username', 'password' будут определены в другом месте (например с помощью KeyValue loader из json файла).
также было бы не плохо сделать ссылки на объекты, например есть MyInterface и есть объекты которые его имплементят A и B, теперь все классы которые на входе ожидают MyInterface надо передавать объекты A
вот как это будет выглядеть
return [
MyInterface::class => A::class
]
Спустя некоторое время мы захотим в места где требуется MyInterface передавать объект B вместо A, нам надо лишь будет заменить в одном месте и остальное должно будет работать.
Поэтому рекомендованно имена контейнерам давать вида namespace\Class (в json) ну или из php 5.5 Class::class в php файлах или руками полное имя класса с неймспейсом.
На случай если кто то не захочет придерживаться рекомендации где имя контейнера имя объекта он может его переопределить
Вот пример
return [
'test' => ['class' => 'AnotherClass']
];
Когда запросят контейнер с именем test создаться объект с классом AnotherClass а так как не указаны зависимости конструктора то создатся он примерно так new AnotherClass();
Где возможно сделать поддержку кэширования
$locator->test = new Lazy(function ($arg) {
// любой код
return $arg;
});
// Удачный способ вызова
$test = $locator->test;
$result = $test(123);
// Даже так удачно
$result = $locator['test'](123);
// А вот так неудачно
$result = $locator->test(123);
Как вариант можно определить функцию __call и в ней разрешать зависимость
Возможно стоит создать Loader который будет загружать зависимости по их имени если у объект реализует LoadableInterface.
В LoadableInterface сделать статичный метод который будет разрешать зависимость и возвращать результат.
Который будет в случае необходимости создавать новый контейнер, а если контейнер с таким именем определен будет его заменять собой, поправить документацию
Имя контейнера только строка
Сделать фаил конфигурации
Посмотреть советы по коду
Внедрить картинки с оценкой качества кода и покрытия тестами в Readme.md
https://scrutinizer-ci.com
На главной странице документации слишком много информации, нужно уменьшить содержимое readme, а остальные примеры и так далее перенести в /doc папку, а на главной странице оставить только оглавление и ссылке на соответсвующую доку.
Излагать документацию от простого (самого часто и гарантированно используемого) к сложному (менее используемому в очень редких проектах или случаях).
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.