В ответ на комментарии Sébastien Renauld: here's an article on Dependency Injection (DI) and Inversion of Control (IoC) с некоторыми примерами и несколько дополнительных слов по принципу Голливуда (что очень важно при работе над каркасом).
Говорить, что ваши классы никогда не потребуются больше, чем один экземпляр, не означает, что статика обязательна. На самом деле это не так. Если вы просматриваете этот сайт и читаете вопросы PHP, которые касаются Singleton «pattern», вы скоро узнаете, почему одиночные игры - это немного не-нет.
Я не буду вдаваться в подробности, но тестирование и синглеты не смешиваются. Зависимость инъекции, безусловно, стоит поближе. На этот раз я оставлю это.
Чтобы ответить на ваш вопрос:
Ваш exaple (Config::get('test')
) означает, что вы имеете статическое свойство в Config
классе где-то. Теперь, если вы сделали это, как вы говорите, чтобы облегчить доступ к данным, представьте, какой кошмар было бы для отладки вашего кода, если бы это значение должно было где-то меняться ... Это статично, поэтому измените его один раз, и он меняется везде. Выяснить, где он был изменен, может быть сложнее, чем вы ожидали.Тем не менее, это ничего по сравнению с теми, кто использует ваш код будет в той же ситуации.
И все же проблемы real начнутся только тогда, когда этот человек, использующий ваш код, хочет проверить, что он сделал: если вы хотите получить доступ к экземпляру в заданном объекте, который был создан в некоторых класс, есть много способов сделать это (особенно в рамках):
class Application
{//base class of your framework
private $defaulDB = null;
public $env = null;
public function __construct($env = 'test')
{
$this->env = $env;
}
private function connectDB(PDO $connection = null)
{
if ($connection === null)
{
$connection = new PDO();//you know the deal...
}
$this->defaultDB = $connection;
}
public function getDB(PDO $conn = null)
{//get connection
if ($this->defaultDB === null)
{
$this->connectDB($conn);
}
return $this->defaultDB;
}
public function registerController(MyConstroller $controller)
{//<== magic!
$controller->registerApplication($this);
return $this;
}
}
Как вы можете видеть, Application
класс имеет метод, который проходит экземпляр Application
к контроллеру, или любой другой частью вашей структуры вы хотите предоставить доступ к области действия класса Application
.
Обратите внимание, что я объявлял свойство defaultDB
как частную собственность, поэтому я использую getter. Я могу, если захочу, передать связь с этим получателем. Конечно, гораздо больше вы можете сделать с этим соединением, но я не могу беспокоиться о написании полной структуры, чтобы показать вам все, что вы можете здесь сделать :).
В принципе, все контроллеры продлят MyController
класса, может быть абстрактным классом, который выглядит следующим образом:
abstract class MyController
{
private $app = null;
protected $db = null;
public function __construct(Application $app = null)
{
if ($app !== null)
{
return $this->registerApplication($app);
}
}
public function registerApplication(Application $app)
{
$this->app = $app;
return $this;
}
public function getApplication()
{
return $this->app;
}
}
Так что в вашем коде, вы можете легко сделать что-то вдоль линий:
$controller = new MyController($this);//assuming the instance is created in the Application class
$controller = new MyController();
$controller->registerApplication($appInstance);
В обоих случаях вы можете получить, что один экземпляр DB как так:
$controller->getApplication()->getDB();
Вы можете протестировать свой фреймворк легко, передав другое соединение БД с методом getDB
, если в этом случае свойство defaultDB
не было установлено. С некоторой дополнительной работой, вы можете зарегистрировать несколько соединений DB в то же время и получить доступ к тем, по желанию, также:
$controller->getApplication->getDB(new PDO());//pass test connection here...
Это, отнюдь не, полное объяснение, но я хотел бы получить этот ответ довольно быстро прежде чем вы окажетесь с огромной статической (и, следовательно, бесполезной) кодовой базой.
В ответ на замечания от OP:
О том, как я бы решить Config
класс. Честно говоря, я бы в значительной степени сделал то же самое, что и с свойством defaultDB
, как показано выше. Но я бы, вероятно, позволит более целенаправленным контроль на какой класс получает доступ к какой части конфигурации:
class Application
{
private $config = null;
public function __construct($env = 'test', $config = null)
{//get default config path or use path passed as argument
$this->config = new Config(parse_ini_file($config));
}
public function registerController(MyController $controller)
{
$controller->setApplication($this);
}
public function registerDB(MyDB $wrapper, $connect = true)
{//assume MyDB is a wrapper class, that gets the connection data from the config
$wrapper->setConfig(new Config($this->config->getSection('DB')));
$this->defaultDB = $wrapper;
return $this;
}
}
class MyController
{
private $app = null;
public function getApplication()
{
return $this->app;
}
public function setApplication(Application $app)
{
$this->app = $app;
return $this;
}
//Optional:
public function getConfig()
{
return $this->app->getConfig();
}
public function getDB()
{
return $this->app->getDB();
}
}
Эти последние два метода на самом деле не требуется, вы могли бы точно так же написать что-то вроде:
$controller->getApplication()->getConfig();
Опять же, этот фрагмент все немного грязный и неполный, но это действительно идет, чтобы показать вам, что вы можете «выставить» некоторых свойства одного класса, путем передачи ссылки на этот класс к другому. Даже если свойства являются частными, вы можете использовать геттеры для доступа к ним одинаково. Вы также можете использовать различные методы регистрации для контроля того, что зарегистрированный объект разрешен для просмотра, как это было сделано с DB-оберткой в моем фрагменте. Класс DB не должен иметь дело с представлениями, пространствами имен или автозагрузчиками. Вот почему я только регистрирую раздел DB в конфиге.
В основном, многие ваши основные компоненты в конечном итоге будут использовать ряд методов. Другими словами, они в конечном итоге реализуют данный интерфейс. Для каждого основного компонента (при условии классического шаблона MVC) у вас будет один абстрактный базовый класс и цепочка наследования из 1 или 2 уровней дочерних классов: Abstract Controller
>DefaultController
>ProjectSpecificController
.
В то же время, все эти классы, вероятно, ожидают, что другой экземпляр будет передан им при построении. Просто посмотрите на index.php
любого проекта ZendFW:
$application = new Zend_Application(APPLICATION_ENV);
$application->bootstrap()->run();
Это все, что вы можете видеть, но внутри приложения, все остальные классы быть инстанцированным. Вот почему вы можете получить доступ к ржут на все из любого места: все классы были экземпляры в другом классе вдоль этих линий:
public function initController(Request $request)
{
$this->currentController = $request->getController();
$this->currentController = new $this->currentController($this);
return $this->currentController->init($request)
->{$request->getAction().'Action'}();
}
Пропустив $this
конструктору класса контроллера, что класс может использовать различные методы получения и установки для получить все, что ему нужно ... Посмотрите на приведенные выше примеры, он может использовать getDB
или getConfig
и использовать эти данные, если это необходимо.
Вот как большинство фреймворков я возился или работал с функцией: приложение запускается в действие и определяет, что нужно делать. Это принцип Голливуда или инверсия управления: приложение запускается, и приложение определяет, какие классы ему нужны, когда. В приведенной ссылке я полагаю, что это сравнивается с магазином, создающим своих собственных клиентов: магазин построен и решает, что он хочет продать. Чтобы продать его, он создаст клиентов, которых он хочет, и предоставит им средства, необходимые для их покупки ...
И, прежде чем я забуду: Да, все это можно сделать без единого статического переменная, не говоря уже о функции, вступает в игру. Я построил свои собственные рамки, и я никогда не чувствовал, что нет другого пути, кроме «go static». Сначала я использовал шаблон Factory, но быстро отбросил его.
IMHO, хорошая структура модульная: вы должны иметь возможность использовать ее (например, компоненты Symfony), без проблем. Использование Factory pattern делает вас считать слишком много. Вы принимаете класс X, который не является данным.
Регистрация тех классов, которые доступны, позволяет использовать гораздо более портативные компоненты. Рассмотрим это:
class AssumeFactory
{
private $db = null;
public function getDB(PDO $db = null)
{
if ($db === null)
{
$config = Factory::getConfig();//assumes Config class
$db = new PDO($config->getDBString());
}
$this->db = $db;
return $this->db;
}
}
В противоположность:
class RegisteredApplication
{//assume this is registered to current Application
public function getDB(PDO $fallback = null, $setToApplication = false)
{
if ($this->getApplication()->getDB() === null)
{//defensive
if ($setToApplication === true && $fallback !== null)
{
$this->getApplication()->setDB($fallback);
return $fallback;//this is current connection
}
if ($fallback === null && $this->getApplication()->getConfig() !== null)
{//if DB is not set @app, check config:
$fallback = $this->getApplication()->getConfig()->getSection('DB');
$fallback = new PDO($fallback->connString, $fallback->user, $fallback->pass);
return $fallback;
}
throw new RuntimeException('No DB connection set @app, no fallback');
}
if ($setToApplication === true && $fallback !== null)
{
$this->getApplication()->setDB($fallback);
}
return $this->getApplication()->getDB();
}
}
Хотя последняя версия немного больше работы, чтобы написать, это совершенно ясно, какой из двух является лучшим выбором. Первая версия просто предполагает слишком много, и не позволяет использовать защитные сетки. Это также довольно диктаторский: предположим, что я написал тест, и мне нужны результаты, чтобы перейти к другой БД. Поэтому мне нужно изменить соединение с БД, для всего приложения (ввод пользователя, ошибки, статистика ... все они, вероятно, будут храниться в БД).
Для этих двух причин лучшим вариантом является второй фрагмент: я могу передать другое соединение с БД, которое перезаписывает приложение по умолчанию, или, если я не хочу этого делать, я могу либо использовать соединение по умолчанию, либо попытайтесь создать соединение по умолчанию. Храните соединение, которое я только что сделал, или нет ... выбор полностью мой. Если ничего не работает, я просто получаю на меня бросок RuntimeException
, но это не главное.
Реми: @ Элиас сказал все в своем ответе, но для вашей части -> get() ...рассмотрели ли вы реализацию интерфейса 'ArrayAccess', который затем позволит вам использовать ваш класс, как если бы он был массивом? –
@ SébastienRenauld, на самом деле нет. Я немного читал об этом несколько дней назад, но я думаю, я должен увидеть и попробовать, как это работает. Если бы вы могли кратко рассказать об этом, что именно он делает? – RemiDG
@ Remi-X: Довольно легко: 'класс Foo реализует ArrayAccess' и [читает документы интерфейса] (http://php.net/manual/en/class.arrayaccess.php). Все _listed_ в интерфейсе должно быть реализовано в классе, и вам хорошо идти –