Reasonable | Understandable | |
---|---|---|
Obvious from reading the code | Documentation / colleague can explain how it works | |
Errors happen at start of execution | Errors can happen during middle of execution | |
Simpler parts, more of them | Fewer parts, but more complex code |
m'kay?
function foo()
{
global $fileUploader;
$fileUploader->putFile();
}
function foo()
{
$storage = new S3Storage();
}
or
function foo()
{
$storage = S3Storage::create();
}
function foo($sl) {
$storage = $sl->get('fileUploader');
…
…
…
if ($criticalError) {
$notification = $sl->get('ErrorNotifier');
}
}
class FooController extends ContainerAware {
function foo() {
$db = $this->get('storage');
}
}
Really a hard coded service locator
function backupFile(S3Storage $s3Storage, EntityManager $em)
{
// ...
}
backupFile($s3Storage, $entityManager);
function backupFile(S3Storage $s3Storage, EntityManager $em)
{
// ...
}
Interfaces are best, so ignore that PSR-2 bye-law
Actually, can we just start calling them types now?
$fooController = new FooController();
// Any code that touches $fooController between constructor
// and setter will behave...poorly
$foo->setDBConnection($dbConnection);
$logger = $di->make('log_service');
vs
$logger = $di->make(Psr\Log\LoggerInterface::class);
class S3Config
{
public $key;
public $secret;
public $region;
...
}
class TmpPath
{
public $value;
function __construct($value) {
$this->value = $value
}
}
https://github.com/rdlowrey/auryn
function backupFile(S3Storage $s3Storage, EntityManager $em)
{
// ...
}
$injector->execute('backupFile');
'Dependency injection container' is a rubbish name. Going to just use the word 'injector' as opposed to saying DIC 100 times.
class BackupController {
function backupFile(...) {
}
}
$instance = $injector->make('BackupController');
$instance->backupFile();
$injector->execute(['BackupController', 'backupFile']);
$route = $router->matchRequest();
$callable = $route->getCallable();
$injector->execute($callable);
$injector->alias('FileUploader', 'S3FileUploader');
$injector->share('S3FileUploader');
$injector->define(
'S3Config',
array(
':aws_key' => 'abcde12345',
':aws_secret' => 'fghij67890'
':region' => 'EU_WEST'
)
);
function createS3Config() {
$key = getenv('aws.s3.key');
$secret = getenv('aws.s3.secret');
$region = getenv('aws.s3.region');
return new S3Config($key, $secret, $region)
}
$injector->delegate('S3Config', 'createS3Config');
function getClientFromSession(Session $session, ClientRepository $clientRepo) {
$clientID = $session->getClientID();
return $clientRepo->getClientByID($clientID);
};
function createFileUploader(Client $client, Injector $injector) {
$storageType = $client->getFileUploaderType();
return $injector->make($storageType);
};
$injector->delegate('Client', 'getClientFromSession');
$injector->delegate('FileUploader', 'createFileUploader');
function prepareS3Client(S3Client $s3Client) {
$s3Client->setTimeOut(10000);
}
$injector->prepare('S3Client', 'prepareS3Client');
function foo(ObjectCache $cache = null)
{
if ($cache) {
if ($value = $cache->get('bar')) {
return $value;
}
}
$value = bar();
if ($cache) {
$cache->put('bar', $value);
}
return $value;
}
function foo(ObjectCache $cache)
{
if ($value = $cache->get('bar')) {
return $value;
}
$value = bar();
$cache->put('bar', $value);
return $value;
}
class NullCache implements ObjectCache
{
function put($key, $object)
{
}
function get()
{
return null;
}
}
Q: Does this break LSP?
“What is wanted here is something like the following substitution property: If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.”
I think this turns on whether you interpret 'behaviour' as either 'technical behaviour' or as 'conforming to the business needs behaviour'.
Taken from http://www.objectmentor.com/resources/articles/lsp.pdf
class RouteCollection {
public function __construct(RouteFactory $route_factory = null) {
if ($route_factory == null) {
$route_factory = new Aura\Router\RouteFactory();
}
}
}
function backupFile(S3Storage $s3Storage,
Request $request, \Doctrine\ORM\EntityManager $entityManager)
{
$backupRepository = $entityManager->getRepository('Backup');
$sourceFile = $backupRepository->getNextFilename();
$backupFilename = $sourceFile.'_'.date("Y_m_d_H_i_s");
try {
$s3Storage->putFile($sourceFile, $backupFilename);
}
catch(S3Exception $s3Exception) {
return new ServerErrorResponse("Somethings borked");
}
$responseData = ['backupFilename' => $backupFilename];
$acceptHeader = $request->getHeader('Accept');
if (strcmp($acceptHeader, "text/xml") === 0) {
return new XMLResponse($responseData);
}
return new JsonResponse($responseData);
}
function backupFile(\Doctrine\ORM\EntityManager $em)
{
...
$backupRepository = $entityManager->getRepository('Backup');
...
}
function backupFile(BackupRepository $backupRepository) { ... }
The Law of Demeter - "Only talk to your immediate friends."
function getBackupRepository(EntityManager $em)
{
return $em->getRepository('Backup');
}
$injector->delegate(BackupRepository::class, 'getBackupRepository');
function backupFile(..., Request $request, ...)
{
...
$acceptHeader = $request->getHeader('Accept');
if (strcmp($acceptHeader, "text/xml") === 0) {
return new XMLResponse($responseData);
}
return new JsonResponse($responseData);
}
class RequestResponseFactory implements ResponseFactory
{
private $request;
function __construct(Request $request) {
$this->request = $request;
}
function create(array $responseData) {
$acceptHeader = $this->request->getHeader('Accept');
if (strpos($acceptHeader, "text/xml") === 0) {
return new XMLResponse($responseData);
}
return new JsonResponse($responseData);
}
}
$injector->alias('ResponseFactory', 'RequestResponseFactory');
function backupFile(S3Client $s3Client, ...)
{
...
try {
$s3Client->putFile($sourceFile, $backupFilename);
}
catch(S3Exception $s3Exception) {
return new ServerErrorResponse("Somethings borked");
}
...
}
interface FileUploader {
function putFile($srcFilename, $dstFilename);
}
This is the D in SOLID. "Dependency inversion principle" == "Depend upon Abstractions. Do not depend upon concretions.”
class S3FileUploader implements FileUploader
{
function __construct(S3Client $s3Client) {
$this->s3Client = $s3Client;
}
function putFile($sourceFilename, $backupFilename) {
try {
$this->s3Client->putFile($sourceFilename, $backupFilename));
}
catch(\Exception $exception) {
throw new FileUploaderException(
"FileUploader exception: ".$exception->getMessage(),
$exception->getCode(),
$exception
);
}
}
}
function backupFile(FileUploader $fileUploader,
ResponseFactory $responseFactory,
BackupRepository $backupRepository)
{
$sourceFile = $backupRepository->getNextFilename();
$backupFilename = $sourceFile.'_'.date("Y_m_d_H_i_s");
try {
$fileUploader>putFile($sourceFile, $backupFilename);
}
catch(FileResponseException $exception) {
return new ServerErrorResponse("Somethings borked");
}
return $responseFactory->create(['backupFilename' => $backupFilename]);
}
function archiveOldData(DBConnection $liveDB, DBConnection $archiveDB) {
...
}
This is a form of flow-based programming......probably.
http://www.jpaulmorrison.com/fbp/
No time....example app is at www.github.com/danack/Tier
"The ability to bind the lifecycle and interactions of stateful components to well-defined but extensible lifecycle contexts."
class MarketContext {
public $tradeExecutor;
public $logManager;
public $priceValidator;
}
Stops lower tier code from being able to look up services, making it easier to reason about what is injected. Probably useful for views.
http://accu.org/index.php/journals/246
$container['session_storage'] = function ($c) {
return new SessionStorage('SESSION_ID');
};
$container['session'] = function ($c) {
return new Session($c['session_storage']);
};
interface Info {
static function getInfo();
}
class Foo implements Info {
static function getInfo() {
}
}
“For years there has been a theory that millions of monkeys typing at random on millions of typewriters would reproduce the entire works of Shakespeare. The Internet has proven this theory to be untrue.”