Upload
michael-stowe
View
3.281
Download
0
Tags:
Embed Size (px)
DESCRIPTION
One of the newest and most powerful features in PHP 5.4 is Traits, or the ability to implement horizontal design into your application. In these slides we will take a look at what traits are and how to use them (including examples of when to use horizontal design) as well as review the ReflectionClass and how it can be used to describe the traits in your code base.
Citation preview
Traits & Horizontal Design
January 17, 2014
michael stowe
MIKESTOWE
• Open Source Contributor
• Author, Speaker, and Consultant
• 10+ years experience hacking PHP
• Zend Certified PHP 5.3 Software Engineer
• Developer Advocate with Constant Contact
.com @mikegstowe
WHAT WE’RE GONNA TALK ABOUT • Traits – What are they?
• What is Horizontal Design?
• Theory
• Sample Vertical Application
• Horizontal Application
• Benefits of Horizontal Design
• Challenges with Horizontal Design
• Using Traits in PHP
• Trait vs. Classes, Abstracts, and Interfaces
• Trait Hierarchy and Class Overrides
• Trait Method Aliasing
• Adjusting Visibility
• Using Properties in Traits
• Trait Functions
• Traits and the Reflection Class
WHAT ARE TRAITS
“Traits are a mechanism for code reuse in
single inheritance languages such as PHP. A
Trait is intended to reduce some limitations of
single inheritance by enabling a developer to
reuse sets of methods freely in several
independent classes living in different class
hierarchies.”
WHAT ARE TRAITS
“[They are] similar to a class, but intended to
group functionality in a fine-grained and
consistent way. It is not possible to
instantiate a Trait on it’s own. It is an
addition to traditional inheritance and enables
horizontal composition of behavior.”
HUH?
To think of traits in another way, think of
traits as characteristics. People have unique
traits that makeup who they are- hair color,
eye color, height, weight, likes, dislikes, etc.
But it is the combination of these traits
working together that make them who they
are.
HUH?
In the same way Traits in PHP are collections
of methods that can be imported into classes.
Each stands alone by itself (ie brown hair),
but becomes part of something more when
inherited into the class (he has brown hair
and brown eyes).
HUH?
Traits are designed to allow for code
reusability and extendibility. These groups of
methods are designed to be pulled in and
used by any other class. Like Midas’ touch,
they seamlessly become part of the class
utilizing them.
QUICK LOOK <?php The Trait
The Class Utilizing the Trait
<?php trait MyTrait { public function test() { /* ... */ } }!
<?php class MyClass { use MyTrait; /* now contains test method */ }!
THE IDEA BEHIND HORIZONTAL DESIGN
The concept between horizontal design/
horizontal reuse is simple, reduce duplicate
code while creating more extendable
applications.
THE IDEA BEHIND HORIZONTAL DESIGN Traditional Vertical Design
Interface
Parent Class
Child Class
THE IDEA BEHIND HORIZONTAL DESIGN
Traditional vertical design is very limiting. To help
address these limitations there has been an increased
focus on Dependency Injection, or injecting classes into
another class to allow the class to access additional
methods and properties. However, even this approach
is very limited in its capabilities. It’s important to note
that Horizontal Design does not replace Dependency
Injection, but rather ensures correct usage of it.
THE IDEA BEHIND HORIZONTAL DESIGN Vertical Design + Horizontal Design
Interface
Parent Class
Child Class
Trait
Trait
Trait
Trait
THE IDEA BEHIND HORIZONTAL DESIGN Traits can reach other traits via the Class
<?php trait traitOne { public function a() { echo 'Trait One Method'; } } trait traitTwo { public function b() { return $this->a(); } } class myClass { use traitOne; use traitTwo; } $a = new myClass(); $a->b(); // echos Trait One Method !
THE IDEA BEHIND HORIZONTAL DESIGN Incredible Code Access and Reusability
Interface
Parent Class
Child Class
Trait
Trait
Trait
Trait
Traits can access other trait methods/ properties of child and parent classes
THE IDEA BEHIND HORIZONTAL DESIGN Traits can be used in a vertical manner
<?php trait traitOne { public function a() { echo 'Trait One Method'; } } trait traitTwo { public function a() { return parent::a(); } } class parentClass { use TraitOne; } class myClass extends parentClass { use traitTwo; } $a = new myClass(); $a->a(); // echos Trait One Method !
THE IDEA BEHIND HORIZONTAL DESIGN Incredible Code Access and Reusability
Interface
Parent Class
Child Class
Trait
Trait
Trait
Trait
Traits can natively utilize other trait methods/ properties via the classes
THE IDEA BEHIND HORIZONTAL DESIGN Traits seamlessly work together with encapsulation
<?php trait traitOne { public function a() { echo 'Trait One Method'; } } trait traitTwo { public function b() { return $this->a(); } } class parentClass { use TraitTwo; } class myClass extends parentClass { use traitOne; } $a = new myClass(); $a->b(); // echos Trait One Method !
<?php trait traitOne { public function a() { echo 'Trait One Method'; } } trait traitTwo { public function b() { return $this->a(); } } class parentClass { use TraitOne; } class myClass extends parentClass { use traitTwo; } $a = new myClass(); $a->b(); // echos Trait One Method !
BUILDING THE APP
In the next few slides we will take a look at
the difference between building a simple app
vertically, verses horizontally.
The app will utilize the same 5 files in both
cases.
BUILDING A HORIZONTAL APP
/** * @package ressf * @category ressf */ class ressf { use ressf\base\validators; use ressf\base\extenders; use ressf\plugins\validators; use ressf\plugins\extenders;!
Pull in the Traits, just like you would namespaces only within the class
Full source on GitHub: http://github.com/mikestowe/ressf Branch: master
<?php
BUILDING A VERTICAL APP
use ressf\plugins\extenders; use ressf\plugins\validators; /** * @package ressf * @category ressf */ class ressf {!
Setup namespaces for class inclusion
Full source on GitHub: http://github.com/mikestowe/ressf Branch: php53
<?php
BUILDING A HORIZONTAL APP
/** * Construct * @return \ressf */ public function __construct() { $this->tags = array_merge($this->baseTags, $this->tags); $this->extenders = array_merge($this->baseExtenders, $this->extenders); }!
Setup the __construct() method to do some basic work
Full source on GitHub: http://github.com/mikestowe/ressf Branch: master
<?php
BUILDING A VERTICAL APP
/** * Construct * @return \ressf */ public function __construct() { $this->extendersClass = new extenders($this); $this->validatorsClass = new validators($this); $this->tags = array_merge($this->validatorsClass->baseTags, $this->validatorsClass->tags); $this->extenders = array_merge($this->extendersClass->baseExtenders,
$this->extendersClass->extenders); }!
In the __construct() method setup properties to store the Extenders and Validators objects. Since these objects will manipulate and make use of properties and methods within the instantiated ressf object we need to pass the ressf object to them as well. Now we can do the basic operations, utilizing the properties we just setup.
Full source on GitHub: http://github.com/mikestowe/ressf Branch: php53
<?php
BUILDING A VERTICAL APP
namespace ressf\plugins; use ressf\base\extenders as baseExtenders; /** * User Defined Extenders Trait * @package ressf * @category ressf/plugins */ class extenders extends baseExtenders { protected $ressf; public function __construct($ressf) { $this->ressf = $ressf; }!
Do the same for the for user defined extenders and validators classes, both which will need to extend the system defined base class:
Full source on GitHub: http://github.com/mikestowe/ressf Branch: php53
<?php
BUILDING A VERTICAL APP
/** * Return the View * @return string */ public function getView() { return $this->view; } /** * Set the View * @param string * @return \ressf */ public function setView($view) { $this->view = $view; return $this; } /** * Set Kill Process Switch * @param bool * @return \ressf */ !
Also setup getters and setters for these properties:
Full source on GitHub: http://github.com/mikestowe/ressf Branch: php53
<?php
BUILDING A HORIZONTAL APP
/** * Handle Extenders * @param string * @return string */ private function handleExtenders($action) { if ($action == 'retrieve') { preg_match_all('/\[ressf:([A-Za-z]+)=([^\]]+)]/', $this->view, $matches); for ($i = 0; $i < count($matches[0]); $i++) { $this->{'set' . ucfirst($matches[1][$i])}($matches[2][$i]); $this->view = str_replace($matches[0][$i], '', $this->view); } }!
Because traits become part of the class, we can call the methods natively
Full source on GitHub: http://github.com/mikestowe/ressf Branch: master
<?php
BUILDING A HORIZONTAL APP
/** * Set Cache Extender * @param string * @return void */ public function setApcCache($cache = 'false') { $base = $this; $this->extenders['cache'] = array( 'doCache' => ($cache != 'false' && $cache != '0'), 'md5' => $base->detect() . '_' . md5($base->view), ); self::addAction('beforeRender', function() use ($base) { if ($base->extenders['cache']['doCache']) { $cachedView = apc_fetch($base->extenders['cache']['md5'], $isCached); if ($isCached) { $base->view = $cachedView; $base->killProcess = true; } } });!
Which can directly access the main class properties.
Full source on GitHub: http://github.com/mikestowe/ressf Branch: master
<?php
BUILDING A VERTICAL APP
/** * Handle Extenders * @param string * @return string */ private function handleExtenders($action) { if ($action == 'retrieve') { preg_match_all('/\[ressf:([A-Za-z]+)=([^\]]+)]/', $this->view, $matches); for ($i = 0; $i < count($matches[0]); $i++) { $this->getExtendersClass()->{'set' . ucfirst($matches[1][$i])}($matches[2][$i]); $this->view = str_replace($matches[0][$i], '', $this->view); } }!
Now to call each method we can utilize the object referenced in the other objects property, like so:
Full source on GitHub: http://github.com/mikestowe/ressf Branch: php53
<?php
BUILDING A VERTICAL APP
/** * Set Cache Extender * @param string * @return void */ public function setApcCache($cache = 'false') { $base = $this->ressf; $base->setExtenders('cache', array( 'doCache' => ($cache != 'false' && $cache != '0'), 'md5' => $base->detect() . '_' . md5($base->getView()), )); ressf::addAction('beforeRender', function() use ($base) { $config = $base->getExtenders('cache'); if ($config['doCache']) { $cachedView = apc_fetch($config['md5'], $isCached); if ($isCached) { $base->setView($cachedView); $base->setKillProcess(true); } } });!
Which will in turn talk back to the ressf class like so:
Full source on GitHub: http://github.com/mikestowe/ressf Branch: php53
<?php
A quick breakdown of rewriting the ressf app to be PHP 5.3 compatible using
Dependency Injection (DI) instead of utilizing Traits:
THE DIFFERENCE
195 Additions | 39 Deletions
https://github.com/mikestowe/ressf/compare/php53
THE DIFFERENCE
195 Additions | 39 Deletions
https://github.com/mikestowe/ressf/compare/php53
By using horizontal design we were not only able to make
the application more efficient, we were also able to reduce
the amount of code by 34%! That’s just for a small, fairly
simple application containing just 5 files and 454 lines of
code!
THE BENEFITS
• Reduces the amount of code needed
• Reduces Dependency Injection requirements
• Provides seamless integrations into classes
• Code can be reused by multiple classes
• Allows for properties of dependencies to be included
THE BENEFITS
• Allows for multiple interactions and eliminates vertical
asphyxiation/ dead ends
• Plug and play friendly (traits play with other traits as long
as there are no conflicts)
• No class name conflicts (as with namespaces)
THE CHALLENGES
• Property Conflicts – properties in traits and calling classes
must be exactly the same otherwise an error is thrown
• Method Conflicts/ Overrides – traits containing the same
methods may result in the wrong method being called
• Harder to navigate code (ie locating methods/ properties)
• Limited to PHP 5.4+
THE DIFFERENCE BETWEEN CLASSES, ABSTRACTS, INTERFACES Traits are similar to abstract classes in that they cannot be
instantiated by themselves. However, unlike an abstract
class traits are not extendable. You can use traits within
other traits however, just as you would in a class through the
use keyword.
Traits can contain methods and properties, something that
makes them unique in PHP.
TRAIT HIERARCHY AND CLASS OVERRIDES Because traits are applied at a horizontal level there is no
hierarchy applied to the methods. Rather, if two traits on the
same vertical plane contain identically named methods a
fatal error will be thrown explaining which method could not
be applied to the class.
If a class has an identically named method it will override
the trait method.
CLASS OVERRIDES
<?php trait myTrait { public function test() { echo 'trait'; } } class myClass { use myTrait; public function test() { echo 'class'; } } (new myClass())->test(); !
Since the class contains the same method as the trait…
The class method will be used, resulting in the script echoing out “class”
<?php
CLASS OVERRIDES
trait traitTwo { public function a() { return parent::a(); } }!
However, if a trait is included in a child class, and shares the same method name with a method in the parent class, the trait will override the parent method, just as if the child class had the same method as the parent class. In this case parent methods will need to be referred to using the parent::method() syntax.
<?php
TRAIT ALIASING
To use traits containing identically named methods or to make a trait method available within a class we can alias the trait using the as and insteadof keywords. The as keyword will give the method an alias to reference it by The insteadof keyword tells the compiler to use that traits method instead of a different identically named method from another trait.
TRAIT ALIASING <?php trait traitOne { public function test() { echo 'one'; } } trait traitTwo { public function test() { echo 'two'; } } class myClass { use traitOne, traitTwo { traitTwo::test insteadof traitOne; traitOne::test as one; } } (new myClass())->one(); (new myClass())->test(); !
<?php
TRAIT ALIASING
When using multiple use declarations within your class it is important to place the insteadof delcaration before the as declarations to avoid conflicts.
<?php /* … */ class myClass { use traitOne, traitTwo { traitTwo::test insteadof traitOne; traitOne::test as one; } } !
<?php
TRAIT ALIASING
class myClass { use traitOne, traitTwo { traitOne::test as one; traitTwo::test insteadof traitOne; } use traitThree, traitFour { traitFour::testme insteadof traitThree; traitThree::testme as three; } } (new myClass())->one(); (new myClass())->test(); (new myClass())->three(); (new myClass())->testme(); !
Using multiple use statements:
<?php
TRAIT METHOD VISIBILITY You can also modify the visibility state of a trait’s methods through the use declaration:
<?php trait traitOne { public function test() { echo 'one'; } } trait traitTwo { public function test() { echo 'two'; } } class myClass { use traitOne, traitTwo { traitOne::test as protected one; traitTwo::test insteadof traitOne; } }!
<?php
TRAIT METHOD DEPENDENCIES Often times a trait method may require interaction with a class method. To ensure the class method is defined you can create an abstract method in the trait, which if called will throw a fatal error:
<?php trait myTrait { public function test() { $this->doSomething(); } public abstract function doSomething(); } class myClass { use myTrait; } (new myClass())->test();!
<?php
TRAIT PROPERTIES
One of the advantages of traits in PHP over other languages is that PHP allows traits to contain properties. This allows you to pull in a full set of code and it’s dependencies without having to manually add properties to your calling class. However, it is important to be careful when using properties as any properties declared in both the trait and the calling class must be identical in visibility, type, and value. The exception to this is when using a data-type of string for one, and a data-type int for another, as long as the state and value are identical.
TRAIT PROPERTIES
Because the visibility state of $test is different, the properties are incompatible and PHP throws a fatal error.
<?php trait myTrait { public $test = 'hi'; } class myClass { use myTrait; protected $test = 'hi'; } new myClass(); !
<?php
TRAIT PROPERTIES
Because the value of $test is different, the properties are incompatible and PHP throws a fatal error.
<?php trait myTrait { public $test = 'hi'; } class myClass { use myTrait; public $test = 'bye'; } new myClass(); !
<?php
TRAIT PROPERTIES
This example however will work because the property state, type, and value is the same.
<?php trait myTrait { public $test = 'hi'; } class myClass { use myTrait; public $test = 'hi'; } new myClass(); !
<?php
TRAIT FUNCTIONS
trait_exists() – similar to class_exists(), this function checks to see if the trait has been defined and returns an boolean. get_declared_traits() – similar to get_declared_classes(), this function returns an array of traits that have been declared.
REFLECTION CLASS
getTraits() returns an array of all traits used in a class, while getTraitNames() returns an array of the trait names in a class. getTraitAliases() returns an array of aliases linked to its original trait and method. isTrait() returns whether or not a tested object is a trait
http://php.net/manual/en/class.reflectionclass.php
MORE USE CASES
MORE USE CASES
BASE CONTROLLER
MOD. CONTROLLER
API CONTROLLER
ACTUAL
What happens if we need to modify the modified controller? What if the API Controller needs new dependencies because our application was updated?
MORE USE CASES
BASE CONTROLLER
MOD. CONTROLLER
API CONTROLLER
ACTUAL
Traits allow us to pull in classes, properties, and methods without having to create a dependency chain, leaving the modified controller easily accessible and updatable.
MORE USE CASES
BASE CONTROLLER
MOD. CONTROLLER
API CONTROLLER
ACTUAL
Traits also allow us to modify existing classes without overriding them in the vertical hierarchy. And keeps source code isolated and reusable.
MODIFIERS
More Information:
http://php.net/manual/en/language.oop5.traits.php
GET THE ARTICLE
http://webandphp.com/issue-3
THANK YOU.
@mikegstowe
visit mikestowe.com/slides for more on PHP and Web Development
@ctct_api
A big thank you to Constant Contact for making this presentation possible