57
Traits & Horizontal Design January 17, 2014 michael stowe

Traits and Horizonal Design

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

Page 1: Traits and Horizonal Design

Traits & Horizontal Design

January 17, 2014

michael stowe

Page 2: Traits and Horizonal Design

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

Page 3: Traits and Horizonal Design

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

Page 4: Traits and Horizonal Design

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.”

Page 5: Traits and Horizonal Design

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.”

Page 6: Traits and Horizonal Design

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.

Page 7: Traits and Horizonal Design

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).

Page 8: Traits and Horizonal Design

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.

Page 9: Traits and Horizonal Design

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 */ }!

Page 10: Traits and Horizonal Design

THE IDEA BEHIND HORIZONTAL DESIGN

The concept between horizontal design/

horizontal reuse is simple, reduce duplicate

code while creating more extendable

applications.

Page 11: Traits and Horizonal Design

THE IDEA BEHIND HORIZONTAL DESIGN Traditional Vertical Design

Interface

Parent Class

Child Class

Page 12: Traits and Horizonal Design

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.

Page 13: Traits and Horizonal Design

THE IDEA BEHIND HORIZONTAL DESIGN Vertical Design + Horizontal Design

Interface

Parent Class

Child Class

Trait

Trait

Trait

Trait

Page 14: Traits and Horizonal Design

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 !

Page 15: Traits and Horizonal Design

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

Page 16: Traits and Horizonal Design

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 !

Page 17: Traits and Horizonal Design

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

Page 18: Traits and Horizonal Design

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 !

Page 19: Traits and Horizonal Design

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.

Page 20: Traits and Horizonal Design

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

Page 21: Traits and Horizonal Design

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

Page 22: Traits and Horizonal Design

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

Page 23: Traits and Horizonal Design

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

Page 24: Traits and Horizonal Design

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

Page 25: Traits and Horizonal Design

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

Page 26: Traits and Horizonal Design

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

Page 27: Traits and Horizonal Design

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

Page 28: Traits and Horizonal Design

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

Page 29: Traits and Horizonal Design

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

Page 30: Traits and Horizonal Design

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

Page 31: Traits and Horizonal Design

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!

Page 32: Traits and Horizonal Design

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

Page 33: Traits and Horizonal Design

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)

Page 34: Traits and Horizonal Design

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+

Page 35: Traits and Horizonal Design

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.

Page 36: Traits and Horizonal Design

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.

Page 37: Traits and Horizonal Design

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

Page 38: Traits and Horizonal Design

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

Page 39: Traits and Horizonal Design

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.

Page 40: Traits and Horizonal Design

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

Page 41: Traits and Horizonal Design

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

Page 42: Traits and Horizonal Design

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

Page 43: Traits and Horizonal Design

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

Page 44: Traits and Horizonal Design

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

Page 45: Traits and Horizonal Design

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.

Page 46: Traits and Horizonal Design

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

Page 47: Traits and Horizonal Design

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

Page 48: Traits and Horizonal Design

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

Page 49: Traits and Horizonal Design

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.

Page 50: Traits and Horizonal Design

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

Page 51: Traits and Horizonal Design

MORE USE CASES

Page 52: Traits and Horizonal Design

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?

Page 53: Traits and Horizonal Design

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.

Page 54: Traits and Horizonal Design

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

Page 55: Traits and Horizonal Design

More Information:

http://php.net/manual/en/language.oop5.traits.php

Page 56: Traits and Horizonal Design

GET THE ARTICLE

http://webandphp.com/issue-3

Page 57: Traits and Horizonal Design

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