Класс A
зависит от
класса B
Двери с электронным замком и кнопка открытия
public function actionOpen() { $result = false; $canBeOpened = HourSettings::find()->select('can_be_opened') ->where(['hour' => date('H')])->scalar(); if ($canBeOpened) { $fd = dio_open('/dev/ttyS0', O_RDWR | O_NOCTTY | O_NONBLOCK); dio_fcntl($fd, F_SETFL, O_SYNC); dio_tcsetattr($fd, ['baud' => 9600, 'bits' => 8]); dio_write($fd, "OPEN\n"); $result = dio_read($fd, 3) === "1\n"; dio_close($fd); } return $this->render('open', ['result' => $result]); }
class DoorManager { public function open() { $door = new Door(); return $door->open(); } }
public function actionOpen() { $doorManager = new DoorManager(); return $doorManager->open(); }
actionOpen() зависит от DoorManager
DoorManager зависит от Door
Выдать всем RFID и сделать логирование!
class DoorManager { public function open(RfidKey $rfid) { $door = new Door(); $log = new EmailAccessLogger('alert@superdoors.com'); $auth = new RfidAuthenticator($rfid, [ 'users' => User::find()->where(['active' => 1])->all() ]); if ($auth->canOpenDoor($door)) { $log->accessGranted($door, $rfid); return $door->open(); } else { $log->accessDenied($door, $rfid); return false; } } }
Win:
|
Lose:
|
class DoorManager { private $door; // key, auth, log public function __construct(RfidKey $key) { $this->key = $key; $this->door = new Door(); $this->log = new EmailAccessLogger('alert@superdoors.com'); $this->auth = new RfidAuthenticator($this->key, [ 'users' => User::find()->where(['active' => 1])->all() ]); } public function open() { if ($this->auth->canOpenDoor($this->door)) { $this->log->accessGranted($this->door, $this->rfid); $this->door->open(); } else { $this->log->accessDenied($this->door, $this->rfid); } } public function close() { } // implementation }
Win:
|
Lose:
|
if
внутри объекта Door()
if
перед созданием объекта Door()
master
и magnet-door-master
MagnetDoorManager
Inversion of Control — принцип объектно-ориентированного программирования, используемый для уменьшения связанности в компьютерных программах
interface DoorInterface { public function open(); public function close(); } class Door implements DoorInterface { public function open() { } // implementation public function close() { } // implementation } class MagnetDoor implements DoorInterface { public function open() { } // implementation public function close() { } // implementation } class FakeDoor implements DoorInterface { // for testing purpose public function open() { return true; } public function close() { return true; } }
abstract class AbstractDoorFactory { /** @return DoorInterface */ abstract public function build(); } class DoorFactory extends AbstractDoorFactory { public function build() { return new Door(); } } class MagnetDoorFactory extends AbstractDoorFactory { public function build() { return new MagnetDoor(); } }
class DoorManager { private $door; // key, auth, log public function __construct(DoorInterface $door, RfidKey $key) { $this->door = $door; $this->key = $key; $this->log = new EmailAccessLogger('alert@superdoors.com'); $this->auth = new RfidAuthenticator($this->key, [ 'users' => User::find()->where(['active' => 1])->all() ]); } }
public function actionOpen($keySecret) { $key = new RfidKey($keySecret); $door = (new MagnetDoorFactory())->build(); $doorManager = new DoorManager($door, $key); return $doorManager->open(); }
Win:
|
Lose:
|
Dependency Injection — это набор практик, который помогает строить приложения с низким уровнем связанности между компонентами.
это НЕ только:
|
это:
|
class DoorManager { private $door; private $key; private $auth; private $log; public function __construct(DoorInterface $door) { } public function setKey(KeyInterface $key) { } public function setAuth(AuthManagerInterface $auth) { } public function setLog(LoggerInterface $log) { } }
public function actionOpen($keySecret) { $door = $this->getDoor(); $doorManager = new DoorManager($door); $doorManager->setKey(new RfidKey($keySecret)); // ... return $doorManager->open(); }
Win:
|
Lose:
|
public function open() { if ($this->log !== null) { $this->log->accessGranted($this->door, $this->key); } }
class FakeLogger implements LoggerInterface { public function accessGranted(DoorInterface $door, KeyInterface $key) { return true; } }
class DoorManager { private $door; // key, auth, log public function __construct( KeyInterface $key, DoorInterface $door, AuthManagerInterface $auth, LoggerInterface $log, ) { $this->door = $door; // key, auth, log } }
Win:
|
Lose:
|
public function actionOpen($keySecret) { $key = new RfidKey($keySecret); $door = new MagnetDoor('some-door-id'); $users = User::find()->active()->all(); $auth = new RfidAuthenticator($key, $users); $log = new EmailAccessLogger('alert@superdoors.com'); $doorManager = new DoorManager($key, $door, $auth, $log); return $doorManager->open(); }
actionOpen
actionClose
?...Dependency Injection Container — класс, управляющий созданием объектов других классов.
actionOpen()
public function actionOpen($keySecret) { $key = new RfidKey($keySecret); $door = new MagnetDoor('some-door-id'); $users = User::find()->active()->all(); $auth = new RfidAuthenticator($key, $users); $log = new EmailAccessLogger('alert@superdoors.com'); $doorManager = new DoorManager($key, $door, $auth, $log); return $doorManager->open(); }
class Container { /** @return KeyInterface */ public function getKey($keySecret) { return new RfidKey($keySecret); } /** @return AuthManagerInterface */ public function getAuthenticator($key, $users) { return new RfidAuthenticator($key, $users); } /** @return LoggerInterface */ public function getLogger() { return new EmailAccessLogger('alert@superdoors.com'); } /** @return DoorInterface */ public function getDoor() { return new MagnetDoor('some-door-id'); } /** @return DoorManagerInterface */ public function getDoorManager($keySecret, $users) { $key = $this->getKey($keySecret); $door = $this->getDoor(); $auth = $this->getAuthenticator($key, $users); $logger = $this->getLogger(); return new DoorManager($key, $door, $auth, $logger); } }
public function actionOpen($keySecret) { $container = new Container(); $users = User::find()->active()->all(); $doorManager = $container->getDoorManager($keySecret, $users); return $doorManager->open(); }
Win:
|
Lose:
|
Зачем мы каждый раз создаём объекты, которые не изменятся в контексте запроса?...
Что еще сюда написать?
Yii::$app->get('something')
Yii::createObject()
Yii::$container->get('something')
Yii::$container->set()
Yii::$app->get('something')
components
Yii::createObject()
Каждый модуль - отдельный Service Locator!
Yii::$container->get('something')
Yii::$container->set()
Yii::createObject()
для создания объектовfunction getDependencies($class) { $dependencies = []; $reflection = new ReflectionClass($class); $constructor = $reflection->getConstructor(); if ($constructor !== null) { foreach ($constructor->getParameters() as $param) { $c = $param->getClass(); if ($c instanceof \ReflectionClass) { $dependencies[] = $c->getName(); } } } return $dependencies; }
Используя Service Locator, мы явно запрашиваем зависимости и зависимы от самого локатора.
Используя DiC, что-то даёт зависимости в конструктор и нам всё равно, что это.
Конфигурируем DiC
$c = Yii::$container; // interface to concrete implementation $c->set(KeyInterface::class, RfidKey::class); $c->set(AuthManagerInterface::class, RfidAuthenticator::class); // interface to component from Service Locator $c->set(LoggerInterface::class, function () { return Yii::$app->get('log'); }); $c->set(DoorInterface::class, function () { return Yii::$app->get('door'); }); // interface to concrete implementation - complex initialisation $c->set(DoorManagerInterface::class, function ($cont, $params, $config) { // DoorManager constructor params order: // 0 - Key, 1 - Door, 2 - Auth, 3 - Logger $params[2] = $cont->get(AuthManagerInterface::class, [$params[0]]); return $cont->get(DoorManager::class, $params, $config); });
public function actionOpen($keySecret) { $container = Yii::$container; $key = $container->get(KeyInterface::class, [$keySecret]); $doorManager = $container->get(DoorManagerInterface::class, [$key]); return $doorManager->open(); }
Win:
|
Lose:
|
new Foo()
→ Yii::createObject()
Yii::$container->get(Foo::class)
Yii::$container->set('yii\widgets\LinkPager', ['maxButtonCount' => 5]);
Yii::$container->set('yii\widgets\ActiveField', ['template' => '...']);