Code Monkey home page Code Monkey logo

mydi's Introduction

mydi's People

Contributors

bagart avatar kefzce avatar smpl avatar telless avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Forkers

telless janklod84

mydi's Issues

Загрузка из xml (+xsd)

Сделать загрузку из xml файла конфигурации контейнеров
формат описать после реализации и сделать xsd для валидации и небольших подсказка в ide

Непонятное поведение магии в php

Заметил прикольное поведение в 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');  // Вот тут разрешение зависимости начинает не с магии

Отказ от доступа к контейнерам как к свойствам

Изначально эта возможность вводилась как легкий способ добавить 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 и поправить документацию

Бесконечное разрешение зависимостей

Ситуация когда А зависит от Б, а Б в свою очередь зависит от А и они вызывают друг друга до бесконечности, например:

$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 и Б

Loader из php файлов с максимальной возможность менять поведение в ходе работы и ленивой загрузкой

Реализовать загрузку файлов из php файлов примерный формат файла

return 75;

Или сложная зависимсоть

use smpl\mydi\Container\Factory;
return new Factory(function () { return new stdClass;});

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

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

в методе resolve при разрешение зависимости имя которой не определено, в сообщение выводить имя этой зависимости sprintf('name is already exist, $s', $name) заменить на sprintf('name is already exist, %s', $name)

Построение сложных зависимостей

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

Сделать возможность создавать разные типы контейнеров для разрешения зависимости.
Минимум:

  • Сервис (Service) - Вызывает логику один раз при запросе контейнера, сохраняет результат и выдает этот результат в дальнейшем.
  • Фабрика (Factory) - Вызывает логику каждый раз.

Логика разрешения зависимости будет в виде обыкновенной анонимной функции если для разрешения зависимости необходимы другие контейнеры пусть через замыкание получает LocatorInterface и через него разрешает остальные зависимости.

Для контейнеров создадим простой ContainerInterface и его будем передавать LocatorInterface в качестве типов контейнеров, а реализация будет разной для каждого типа.

При этом в LocatorInterface если будет передаваться анонимная функция, будем считать что требуется Service

Примеры кода в документации покрыть тестами

Для этого в документации не будет кода, а будут ссылки на файлы расположенные в tests/resource/ там будут лежать примеры кода разбитые по файлам, которые будут тестироваться функциональными тестами.

isExist возвращает false хотя контейнер может быть загружен через Loader

в методе isExist и соответсвенно в isset($locator['containerName']) можно получить ответ false в случае если зависимость определена но ещё пока не загруженна, что является неверным, надо проверять может ли она быть загружена через Loader и если может то возвращать true но не загружать её пока не запросят

Ошибка в документации

  • В примерах использования LoaderInterface в загрузке на лету не правильное утверждение
    в текущем коде оно вернет true так как зависимость уже будет определена и Loader повторно не вызовется
var_dump(20 === $locator->testContext); // false
  • Основные идеи, 2 пункт опечатка в слове "возвращают"
  • и 3 пункт "другова"

Parser php файлов для KeyValue Loader

Сделать простой способ конфигурации в php файлах как в случае с json
один Loader по загрузке простых типов, другой по загрузке контейнров

Более декларативный синтаксис

Некоторые люди хотели бы для описания зависимостей более простой и декларативный синтаксис.
Например загрузку параметров(в виде ключ значение) из различных форматов #35 ну и поддержку форматов json и php
Загрузку контейнеров и построение зависимостей (где элементы это это уже другие контейнеры)

Функциональные тесты

в директории tests существующие тесты перенести в tests/unit и добавить директорию /tests/functional (здесь зависимости не будут мокаться) оно нужно для общего тестирование библиотеки и чтобы тестировать примеры кода из документации #41

LocatInterface доступ как к массиву и как к свойствам объекта

Было бы не плохо чтобы 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

ну и возможность смешанной работы

Загрузка контейнеров по требованию различными способами (с помощью LoaderInterface)

При использование Locator в большой системе необходимо в начале объявить все контейнеры и потом вызывать все необходимое зависимости, на объявление контейнера создается \Closure объект с анонимной функцией что занимает память, было бы не плохо создавать только те контейнеры которые необходимы и больше ничего не объявлять.
Для загрузки таких контейнеров нужен новый LoaderInterace

interface LoaderInterface {
   /**
    * Проверяет может ли загрузить данный контейнер этот Loader
    */
   public function isLoadable($containerName);
   /**
    * Загрузка контейнера
    */
   public function load($containerName);
}

с помощью LoaderInterface можно сделать загрузку определения контейнеров из php файлов или json или yaml или любого другова формата
и соответственно надо иметь возможность получать и устанавливать эти LoaderInterface в Locator и при resolve контейнера если он еще не определен попытаться его загрузить через Loader который сможет его загрузить

Разделить unit test для locator

На текущий момент тесты для Locator объединен ООП стиль, ArrayAccess и доступ к элементам через __get __set объединены в одном юнит тесте.
Разделить на разные юнит тесты, и подготовить документацию в соответствие с #12

Анонимным функциям Service по умолчанию

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

$locator->test = new Service(function () {
    return 1;
});   // Старый вариант описания
$locator->magic = function () {
    return 1;
};    // Должно быть равно сильно преведущему описанию.

Сделать описание

  • Travis ci вывод прохождения тестов
  • Раздел установки
  • Примеры использования
  • Как применять

В документации Factory как контейнер по умолчанию для анонимной функции

в примерах использования указан некоректный пример

// Просто анонимная функция которая автоматически обернется в Factory, я так предпочитаю создавать
$locator->add(pdo2, function () use ($locator) {
    return new \PDO($locator->add(dsn), $locator->add(user), $locator->add(password));
});

перенести её в раздел сервисов

ещё несколько неточностей в примерах кода

LocatorInterface

Сделать описание для данного интерфейса, за что он отвечает.
Добавить возможность для добавления(add) зависимости.
Добавить возможность для разрешения(resolve) зависимости.

Минимальная версия php 5.5

Отказ от поддержки 5.4 чтобы использовать новые возможности

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

Доступ к LocatorInterface внутри ContainerInterface

Использование use для передачи Locator у функций не очень красиво и возможно не очень правильно.
Гораздо лучше будет если у ContainerInterface в методе resolve появиться аргумент LocatorInterface а как его использовать пусть решает сам контейнер (например передает первым аргументом в вызываемую функцию), вариант с использованием use останется доступным но считать его использование bad practice и только для обратной совместимости

Надо лишь подумать как быть с Lazy контейнером.

Покрытие тестами 100%

В src/loader/File.php не все методы протестированны (покрыть их все тестами) ото покрытие 98%.

Убрать генерацию файлов из юнит тестов

В тестирование различных LoaderInterface иногда создаются файлы конфигурации и по окончанию теста удаляются
Вынести эти файлы в resource пусть будут статичными и не удаляются

Построение дерева зависимостей

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

Для этого в LocatorInterface необходимо добавить метод который будет возвращать ответ в виде:

[
   'containerName' => [
      'dependceContainer1',
      'dependceContainer2',
   ],
   'dependceContainer1' => ['dependceContainer3'],
   'dependceContainer2' => [],
   'dependceContainer3' => [],
];

Логика работы будет следующая вначале он у Locator запрашивает список всех валидных имен контейнеров (объявленных и тех которые можно подгрузить используя Loader) после этого он подгружает каждый контейнер и смотрит какие зависимости он затребовал для своей загрузки у Locator (там есть подобный механизм для защиты от зацикливаний, надо сделать его сохранение).

Добавить метод по получению всех зависимостей в LocatorInterface (объявленных и которые можно подгрузить)

Добавить метод в LoaderInterface который вернет список всех имен контейнеров которые он сможет подгрузить, он будет вызываться для списка контейнеров в LocatorInterface

Тип контейнера Lazy

Хотелось бы иметь возможность использовать отложенную инициализацию

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

$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 уже путь до файла не передавать.

Загрузка параметров(Loader KeyValue) из json файла

Написать LoaderInterace который будет читать файлы различных форматов и хранить данные в виде ключ значения в том виде в котором они были заданы

Для поддрежки разных форматов надо сделать возможность парсинга отдельной (ParserInterface) и для примера сделать json

Также можно перенести тесты в директорию tests/unit и соответсвенно подправить namespace

Не превращать анонимные функции в Service в LocatorInterface

Раньше было удобно превращать анонимные функции в Service #11 потому что так почти всегда они и применялись.
После реализации #22 оборачивать их внутри LocatorInterface не очень удобно иногда удобней чтобы они возвращали анонимные функции которые можно было бы вызывать, а в случае необходимости трансформацию объектов можно сделать в LoaderInterface

Я думаю надо сделать отдельный интерфейс MutationInterface в котором можно будет определить какие объекты и как преобразовывать, его передавать в LoaderInterface который будет автоматически проверять и если необходимо преобразовывать, по умолчанию никаких преобразований не проводить.

Получения списка всех контейнеров у ServiceLocator Loader

Собственно проблема в том что 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();

Проблемы при получение Lazy объектов

$locator->test = new Lazy(function ($arg) {
   // любой код
   return $arg;
});
// Удачный способ вызова
$test = $locator->test;
$result = $test(123);

// Даже так удачно
$result = $locator['test'](123);

// А вот так неудачно
$result = $locator->test(123);

Как вариант можно определить функцию __call и в ней разрешать зависимость

В LocatorInterface добавить метод set

Который будет в случае необходимости создавать новый контейнер, а если контейнер с таким именем определен будет его заменять собой, поправить документацию
Имя контейнера только строка

Изменить раздел документации

На главной странице документации слишком много информации, нужно уменьшить содержимое readme, а остальные примеры и так далее перенести в /doc папку, а на главной странице оставить только оглавление и ссылке на соответсвующую доку.

Излагать документацию от простого (самого часто и гарантированно используемого) к сложному (менее используемому в очень редких проектах или случаях).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.