I present four design patterns that make your development easier and better. Design patterns are a fantastic way to make more readable code, as they make use of common ideas that many developers know and use. These patterns are tried and tested in the enterprise world.
The first one is dependency injection. This covers putting the variables that a class needs to function preferably inside a constructor.
The second one is the factory pattern. A factory moves the responsibility of instantiating an object to a third-party class.
The third one is dependency injection. This allows us to place a class' dependencies at one time, making it easy to come back and see what the class needs to survive.
Finally, we discuss the chain of responsibility. This allows complex operations to be handled by a chain of classes. Each class in the chain determines whether it is capable of handling the request and, if so, it returns the result.
8. DESIGN PATTERN BENEFITS
• Presents a common solution to a problem.
• Common solution = faster implementation
• Recognizable to other developers.
• Encourages more legible and maintainable code.
9. TERMINOLOGY
• Interface:
• An outline or a blueprint for classes. Cannot be instantiated.
• Abstract class:
• A class that can serve as an outline, while providing base
functionality of its own. Can implement an interface. Cannot be
instantiated.
10. TERMINOLOGY
• Concrete class:
• A “creatable” class. Can implement an interface or extend an
abstract or concrete class. Can be instantiated.
12. class Session()
{
function __construct() {
session_start();
}
function getCreatedTime()
{
return $_SESSION[…];
}
}
public function getSession()
{
return new Session();
}
13. $app = new App();
echo $app->getSession()->getCreatedTime();
sleep(2);
echo $app->getSession()->getCreatedTime();
14.
15. SINGLETONS
The way it used to be done: a global means to access
one instance of an object
16. SOLUTION?
• Use static class methods to track single instance
of object:
public static function getInstance()
{
if (!self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
17. SOLUTION?
• Then, get the class like so:
public function getSession()
{
return Session::getInstance();
}
18. Model needs session
class Session
Create instance and
store it.
self::
$_instance?
Return instance.
No
Yes
19. SINGLETON’S PROBLEMS:
• Hard links the session to its consuming class.
• Makes testing more difficult.
• Breaks Single Responsibility Principle, as the class
manages its own instance.
26. DI
• We create the objects and then “inject” them into
each class that depends on them.
• Benefits:
• Easier to follow Single Responsibility Principle.
• Makes testing easier: we can inject mocks or
stubs.
• Good visibility into what each class needs.
27. protected $session;
public function __construct(Session $session)
{
$this->session = $session;
}
public function saveToSession($value)
{
$this->session->save(‘time’, $value);
}
public function getFromSession()
{
return $this->session->get(‘time’);
}
28. $session = new Session();
$app = new App($session);
$app->saveToSession(time());
echo $app->getFromSession();
30. new App(); new Session(); DI Container
class App {
protected $session;
function __construct(Container $container) {
$this->session = $container->get(‘session’);
}
}
31. DI CONTAINERS
• Pimple (pimple.sensiolabs.org)
• Super simple container but is rather manual.
• PHP-DI (http://php-di.org/)
• More complex to setup but injection is
automatic.
• Similar to Symphony Dependency Injection component:
https://github.com/symfony/DependencyInjection
32. USE CASES:
• Database connections
• Sessions
• Other objects in a request’s lifecycle.
35. $controller = new HomeController(
$request,
$response,
$templateEngine,
$cookieJar,
$properties,
// …
);
Too many properties
to call frequently.
36.
37. FACTORY
• We manufacture very little of what we own.
• Factories make most of our possessions for us.
• Single-purposed: making things.
38. FACTORY DEFINED:
• Moves the task of creating classes to a
single-purposed class, the factory.
• Factory provides a simple method to
instantiate new class.
42. FACTORY:
• Create a class as the factory.
• All classes instantiated should extend a
common type or implement a common
interface.
• Return the newly created class.
43. FACTORY:
• Extra kudos: you can use a configuration file
(Yaml/Xml) to control the creation of the
classes.
45. class ProductFactory
{
public static function hydrate($data)
{
switch ($data[‘type’]) {
case ‘simple’:
$product = new SimpleProduct($data);
break;
case ‘configurable’:
$product = new ConfigProduct($data);
break;
}
return $product;
}
}
47. class ProductFactory
{
public static function hydrate($data, $config)
{
$type = $data[‘type’];
$className = $config[‘products’][$type][‘class’];
$product = new $className($data);
return $product;
}
}
48. USE CASES:
• Factory is a design-pattern for instantiating classes.
• Offloading the creation of parameter-heavy
constructors:
• Dependency Injection
• Determine what type of class to setup.
• Gives means for 3rd-party developers to extend
the functionality of your software.
51. PROBLEM:
• How to separate changeable details?
public function persist($data, $to)
{
switch($to) {
case 'mysql':
$connection = new PDO(...);
$connection->exec(...);
break;
case 'salesforce':
$connection = new HttpClient(...);
// ...
break;
}
}
54. PROBLEM:
public function persist($data, $to)
{
switch($location) {
case 'mysql':
// …
case 'salesforce':
// …
case 'redis':
// …
case 'file':
// …
case 'ec2':
// …
}
}
55. PROBLEM:
public function persist($data, $to)
{
switch($location) {
case 'mysql':
// …
case 'salesforce':
// …
case 'redis':
// …
case 'file':
// …
case 'ec2':
// …
case 'dropbox':
// …
}
}
57. UPDATED:
public function persist($data, $to)
{
switch($location) {
case 'mysql':
$this->persistToMysql($data);
case 'salesforce':
$this->persistToSalesForce($data);
case 'redis':
$this->persistToRedis($data);
case 'file':
$this->persistToFile($data);
case 'ec2':
$this->persistToEc2($data);
case 'dropbox':
$this->persistToDrop($data);
}
}
61. STRATEGY
• Parent class handles “what” happens.
• Child class handles “how” it happens.
• Parent doesn’t know who the child is.
• The children must all expose the same methods.
62. • Factory:
• initializes objects, returns them for use
• should expose a similar set of methods
• Strategy:
• uses objects
• must expose a similar set of methods
63. Data Storage (parent: “what happens”)
Provider (child: “how it happens”)
save()load() update()
save()load() update()
list()
67. class DataStorage //parent: “what happens”
{
protected $provider; //child: “how it happens”
public function __construct ($provider)
{
$this->provider = $provider;
}
public function update ($model)
{
$this->provider->update($model);
}
}
69. class MySqlProvider implements ProviderInterface
{
/**
* Child MySqlProvider
*/
public function update($model)
{
$connection = $this->getConnection();
$connection->exec('UPDATE … SET');
return $this;
}
}
70. class SalesForceProvider
{
/**
* Child SalesForceProvider
*/
public function update($model)
{
$client = new GuzzleHttpClient();
$client->request('POST', ‘{url}’);
// ...
}
}
71. class DataStorage { /* … */ }
interface ProviderInterface { /* … */ }
class MysqlProvider { /* … */ }
class SalesForceProvider { /* … */ }
// using our strategy design pattern:
$storage = new DataStorage(new MysqlProvider);
$storage->update($data);
72. STRATEGY USES:
• Any case where different algorithms or methods
are needed to be interchangeable, but only one
used:
• Storage locations
• Computational Algorithms
75. PROBLEM:
• Cascading layers of caching.
• How to get the value from the list?
• Cascading renderers for a component.
• How to have renderers determine who should
render?
76. public function get($key)
{
$redis = new Redis();
$db = new Db();
$file = new File();
if ($redisVal = $redis->get($key)) {
return $redisVal;
} else if ($dbVal = $db->get($key)) {
return $dbVal;
} else if ($fileVal = $$file->get($key)) {
return $fileVal;
}
return '';
}
77. SOLUTION #1: LOOPS
• Easier, but less flexible.
• Iterate through each member of an array of the
items and have that member check to determine
its legibility.
78. public function get($key)
{
$cacheTypes = ['Redis', 'Db', 'File'];
$value = '';
foreach ($cacheTypes as $cacheType) {
$className = new "ModelCache{$cacheType}";
$cache = new $className();
if ($value = $cache->get($key)) {
break;
}
}
return $value;
}
79.
80. SOLUTION #2: COR
• Horizontal chaining: sibling relationships
• Parent class interacts with first item in the chain.
82. File: no next link :(
DB: $this->_nextLink
Redis: $this->_nextLink
83. SOLUTION #2: COR
• Each link inherits from a abstract class.
• Abstract class handles chaining.
• Concrete objects provide functionality for
processing and whether or not to proceed.
88. <?php
abstract class Base
{
protected $_nextLink;
protected $_result;
protected $_data;
abstract protected function getDataFor($key);
public function append(Base $nextLink) { /**/ }
public function get($request) { /**/ }
public function processing($request) { /**/ }
}
89. <?php
abstract class Base
{
protected $_nextLink;
protected $_result;
protected $_data;
abstract protected function getDataFor($key);
public function append(Base $nextLink) { /**/ }
public function get($request) { /**/ }
public function processing($request) { /**/ }
}
90. <?php
abstract class Base
{
protected $_nextLink;
protected $_result;
protected $_data;
abstract protected function getDataFor($key);
public function append(Base $nextLink) { /**/ }
public function get($request) { /**/ }
public function processing($request) { /**/ }
}
91. <?php
abstract class Base
{
protected $_nextLink;
protected $_result;
protected $_data;
abstract protected function getDataFor($key);
public function append(Base $nextLink) { /**/ }
public function get($request) { /**/ }
public function processing($request) { /**/ }
}
92. <?php
abstract class Base
{
protected $_nextLink;
protected $_result;
protected $_data;
abstract protected function getDataFor($key);
public function append(Base $nextLink) { /**/ }
public function get($request) { /**/ }
public function processing($request) { /**/ }
}
93. <?php
abstract class Base
{
protected $_nextLink;
protected $_result;
protected $_data;
abstract protected function getDataFor($key);
public function append(Base $nextLink) {
if (!$this->_nextLink) {
$this->_nextLink = $nextLink;
} else {
$this->_nextLink->append($nextLink);
}
}
public function get($request) { /**/ }
public function processing($request) { /**/ }
public function getSuccessor() { /**/ }
}
94. abstract protected function getDataFor($key);
public function append(Base $nextLink) { /* … */}
public function get($request) {
if (!($request instanceof Request)) {
$key = $request;
$request = new Request();
$request->setKey($key);
}
$success = $this->processing($request);
if (!$success && $this->_nextLink) {
$this->_nextLink->get($request);
}
return $request->getResult();
}
public function processing($request) { /* … */ }
95. abstract protected function getDataFor($key);
public function append(Base $nextLink) { /* … */}
public function get($request) {
if (!($request instanceof Request)) {
$key = $request;
$request = new Request();
$request->setKey($key);
}
$success = $this->processing($request);
if (!$success && $this->_nextLink) {
$this->_nextLink->get($request);
}
return $request->getResult();
}
public function processing($request) { /* … */ }
96. public function get($request) { /* … */ }
public function processing($request) {
$key = $request->getKey();
$value = $this->getDataFor($key);
if ($value) {
$request->setResult($value);
return true;
} else {
return false;
}
}
public function getNextLink() { /* ... */}
}
97. class RedisCache extends Base
{
protected function getDataFor($key)
{
// connects to redis and returns value
}
}
99. USAGE STEPS:
• Call first link’s get method.
• Will check itself.
• If no value, and next link, call that link.
• Repeat…
100. USE CASES:
• Layers of caching
• Rendering views with a standard input, but giving
each member the equal opportunity to display.
• Iterating through strategies to find a solution to an
algorithm.
102. REVIEW
DI Factory Strategy CoR
Class has
everything it needs
on construction.
Creation: Chooses
and creates any
object from options.
Action: Similar
interface for
different
applications.
Interacts with
options to retrieve
output.
All use specific classes to maintain Single Responsibility Principle.
103. RESOURCES
• Design Patterns: Elements of Reusable Object-
Oriented Software
• (Gamma, Helm, Johnson, Vlissides; published by Addison-Wesley)
• Design Patterns PHP
• http://designpatternsphp.readthedocs.org/en/latest/
• PHP the Right Way
• http://www.phptherightway.com/
Great to be with everyone. I have appreciated this, as these events have tremendously improved my coding.
Going to cover three tonight. Hopefully you can implement at least one into your coding practices. Remember when John Kary recommended to have a maximum of 10 lines of code in a function: tremendously cleaned up my code.
Had to put a meme in here, with a tacky transition.
For DI, you can inject anything: variables such as credentials, configuration settings, class names, etc.
Append puts together the chain, get iterates through the chain, processing determines whether the class can handle request, getDataFor($key) retrieves the data and returns it to processing.