PHP traits, treat or threat?
-
Upload
nick-belhomme -
Category
Technology
-
view
15.597 -
download
3
description
Transcript of PHP traits, treat or threat?
PHP traits, treat or threat?
Nick BelhommeJanuary 28th, PHPBenelux Conference 2012, Belgium
Software Architect / Project Lead
Author of the Zend Framework 2.0 Cookbook
International Conference Speaker
Contributor to various Open Source Projects
Freelance PHP Consultant
Market positions
http://w3techs.com/
Market positions
● Python 0.3%
Market positions
● Python 0.3%● Ruby 0.6%
Market positions
● Python 0.3%● Ruby 0.6%● Perl 1.0%
Market positions
● Python 0.3%● Ruby 0.6%
● ColdFusion 1.2%● Perl 1.0%
Market positions
● Python 0.3%● Ruby 0.6%
● ColdFusion 1.2%● Perl 1.0%
● Java 1.2%
Market positions
● Python 0.3%● Ruby 0.6%
● ColdFusion 1.2%● Perl 1.0%
● Java 1.2%● ASP.NET 21.6%
Market positionsMarket positions
● Python 0.3%● Ruby 0.6%
● ColdFusion 1.2%● Perl 1.0%
● Java 1.2%● ASP.NET 21.6%● PHP 77.4%
Java
PHP
ASP.NET
ColdFusion
Perl
Used by many sitesUsed by fewer sites
Use
d by
hig
h tra
ffic
site
sU
sed
by lo
w tr
affic
site
s
PHP Market Position, 11 Jan 2012
PHP Version Adoption
● PHP 5 93.9%● PHP 4 6.1%● PHP 3 < 0.1%● PHP 6 < 0.1%
PHP 5 usage
● Version 5.2 73.8%● Version 5.3 20.2%● Version 5.1 5.8%● Version 5.0 0.2%● Version 5.4 < 0.1%
Slow 5.3 Adoption? ● Shared hosting● Companies refrain from updating stable
systems / distributions● You
Adopt now● More stability● Security● Better engine (ie garbage collection)● New language features● Cleaner code because of more elegant ways of
solving problems
PHP 5.4
● Array short syntax (javascript notation)● Array dereferencing● Class access on instantiation● Indirect method call by array variable● Engine processes OO code faster● <?=● JSONSerializable interface● ...
Traits
ZF Use Case
ZF Code base used is for illustration purposes only and do –not-- express future possible implementations.
ZF2.0 is not yet released so giving ZF3.0 implementations illustrates only my own assumptions.
PHP5.4 stable is not yet released so no real world projects areand should be running this version.
Because of this no real best practices have been defined.
namespace Zend\View;class TemplatePathStack implements TemplateResolver { public function setOptions($options = array()) { if (!is_array($options) && !$options instanceof Traversable) { throw new Exception\InvalidArgumentException( __METHOD__ . ' expects an array or Traversable' ); }
foreach ($options as $key => $value) { $this->setOption($key, $value); } return $this; }
public function setOption($key, $value) { switch (strtolower($key)) { case 'lfi_protection': $this->setLfiProtection($value); break; case 'script_paths': $this->addPaths($value); break; default: break; } }}
namespace Zend\Mvc\Router;class RouteBroker implements Broker{ public function setOptions($options) { if (!is_array($options) && !$options instanceof \Traversable) { throw new Exception\InvalidArgumentException(sprintf( 'Expected an array or Traversable; received "%s"', (is_object($options) ? get_class($options) : gettype($options)) )); }
foreach ($options as $key => $value) { switch (strtolower($key)) { case 'class_loader': // handle this case Default: break;// ignore unknown options } } return $this; }}
Problem: Code Duplication
if (!is_array($options) && !$options instanceof \Traversable) { throw new Exception\InvalidArgumentException(sprintf( 'Expected an array or Traversable; received "%s"', (is_object($options) ? get_class($options) : gettype($options)) )); }
foreach ($options as $key => $value) { //handle each case }
return $this;
Solution in PHP5.4? Multiple Inheritance
● Would provide the setOptions in all child classes needed
● Introduces the diamond problem
NOT an Option!A better solution called Traits
● Would provide the setOptions in all classes that use the trait
● Eliminates the diamond problem
trait Options{ public function setOptions($options) { if (!is_array($options) && !$options instanceof \Traversable) { throw new Exception\InvalidArgumentException(sprintf( 'Expected an array or Traversable; received "%s"', (is_object($options) ? get_class($options) : gettype($options)) )); }
foreach ($options as $key => $value) { $this->setOption($key, $value); }
return $this; }}
namespace Zend\View;class TemplatePathStack implements TemplateResolver{ use Options;
public function setOption($key, $value) { switch (strtolower($key)) { case 'lfi_protection': $this->setLfiProtection($value); break; case 'script_paths': $this->addPaths($value); break; default: break; } }}$templateStack = new TemplatePathStack();$templateStack->setOptions(['lfi_protection' => true]);
namespace Zend\Mvc\Router;class RouteBroker implements Broker{ use Options;
public function setOption($key, $value) { switch (strtolower($key)) { case 'class_loader': // handle this case default: // ignore unknown options break; } }}
$routeBroker = (new RouteBroker)->setOptions(['class_loader'=>'SomeLoader']);
You have just seen traits
Lets dive deeper
get your chest wet
namespace Zend\Loader {use RuntimeException; trait SplRegister { public function register() { spl_autoload_register(array($this, 'autoload')); }
public function unregister() { spl_autoload_unregister(array($this, 'autoload')); } } class StandardAutoloader { use SplRegister;
public function unregister() { throw new RuntimeException( 'you should not unregister the standard autoloader once registered' ); } }}namespace { $loader = new Zend\Loader\StandardAutoloader(); $loader->register(); $loader->unregister(); // will throw the exception}
Precedence Order
The precedence order is that members from the current class
override Trait methods.
namespace Zend\Loader {use RuntimeException; class StandardAutoloader { public function unregister() { throw new RuntimeException( 'you should not unregister the standard autoloader once registered' ); } }}namespace NbeZf\Loader {use Zend\Loader as ZendLoader; class StandardAutoloader extends ZendLoader\StandardAutoloader { use ZendLoader\SplRegister; } }namespace { $loader = new NbeZf\Loader\StandardAutoloader(); $loader->register(); //will register (trait) $loader->unregister(); // will unregister (trait)}
Precedence Order
An inherited member from a base class is overridden by a member inserted by a Trait
Distil Methods!
namespace Zend\Loader {use RuntimeException; class StandardAutoloader { public function unregister() { throw new RuntimeException( 'you should not unregister the standard autoloader once registered' ); } }}namespace NbeZf\Loader {use Zend\Loader as ZendLoader; class StandardAutoloader extends ZendLoader\StandardAutoloader { use ZendLoader\SplRegister { ZendLoader\StandardAutoloader::unregister insteadof ZendLoader\SplRegister; } }}namespace { $loader = new NbeZf\Loader\StandardAutoloader(); $loader->register(); //will register (trait) $loader->unregister(); // will throw RuntimeException (base class)}
insteadof
Can be used to say which method to use instead of the trait method.
Multiple Traits
namespace Zend\Filter { trait Locale { protected $locale;
public function getLocale() { return $this->locale; }
public function setLocale($locale = null) { $this->locale = ZendLocale::findLocale($locale); return $this; } }
trait WhiteSpace { protected $allowWhiteSpace;
public function getAllowWhiteSpace() { return $this->allowWhiteSpace; }
public function setAllowWhiteSpace($allowWhiteSpace) { $this->allowWhiteSpace = (boolean) $allowWhiteSpace; return $this; } } class Alpha extends AbstractFilter { use Locale, WhiteSpace; }}
Multiple Conflicting Traits
The Diamond Problem
(Not yours or a girls best friend!)
namespace Zend\Filter { trait Locale { protected $locale; public function getLocale() { return $this->locale; } public function setLocale($locale = null) { $this->locale = ZendLocale::findLocale($locale); return $this; } }
trait SecondLocale { protected $locale; public function setLocale($locale = null) { if (is_string($locale)) { $locale = array($locale); } elseif ($locale instanceof Locale) { $locale = array($locale->toString()); } elseif (!is_array($locale)) { throw new Exception\InvalidArgumentException( 'Locale has to be string, array or an instance of Zend_Locale' ); } foreach ($locale as $single) { if (!Locale::isLocale($single)) { throw new Exception\InvalidArgumentException("Unknown locale '$single'"); } } $this->_locale = $locale; return $this; } } class Alpha { use Locale, SecondLocale; }}namespace { (new Zend\Filter\Alpha)->setLocale('nl_be');}
Fatal error: Trait method getLocale has not been applied, because there
are collisions with other trait methods
Insteadof
to the rescue
namespace Zend\Filter { trait Locale { protected $locale;
public function getLocale()
public function setLocale($locale = null) }
trait SecondLocale { protected $locale;
public function setLocale($locale = null) }
class Alpha { use Locale, SecondLocale { SecondLocale::setLocale insteadof Locale; } }}namespace { $alpha = new Zend\Filter\Alpha(); $alpha->setLocale('nl_be');}
WIN!
BUTStrict Standards: Zend\Filter\Locale
and Zend\Filter\SecondLocale define the same property ($locale)
in the composition of Zend\Filter\Alpha. This might be
incompatible, to improve maintainability consider using
accessor methods in traits instead.
namespace Zend\Filter { trait Locale { protected $locale = 'nl_BE'; public function getLocale() public function setLocale($locale = null) }
trait SecondLocale { protected $locale = 'en_US'; public function setLocale($locale = null) }
class Alpha { use Locale, SecondLocale { SecondLocale::setLocale insteadof Locale; } }}namespace { $alpha = new Zend\Filter\Alpha(); $alpha->setLocale('nl_BE');}
Fatal error: Zend\Filter\Locale and Zend\Filter\SecondLocale define the
same property ($locale) in the composition of Zend\Filter\Alpha.
However, the definition differs and is considered incompatible.
namespace Zend\Filter { trait Locale { static public $locale = 'en_US'; public function getLocale() { return self::$locale; } public function setLocale($locale = null) { self::$locale = $locale; } }
class Alpha { use Locale; public function get() { return self::$locale; } }}namespace { $alpha = new Zend\Filter\Alpha(); echo $alpha->getLocale(); // en_US echo $alpha->get(); // en_US $alpha->setLocale('nl_be'); echo $alpha->getLocale(); // nl_be echo $alpha->get(); // nl_be}
namespace Zend\Filter { trait Locale { static public $locale = 'en_US'; public function getLocale() { return self::$locale; } public function setLocale($locale = null) { self::$locale = $locale; } }
class Alpha { use Locale; public function get() { return self::$locale; } } class Beta { use Locale; }}namespace { $alpha = new Zend\Filter\Alpha(); echo $alpha->getLocale(); // en_US $alpha->setLocale('nl_be'); echo $alpha->getLocale(); // nl_be $beta = new Zend\Filter\Beta(); echo $beta->getLocale(); //en_US}
Aliassing, useful to replace the parent:: construct
namespace Zend\Mvc\Controller {Use ... trait Stdlib\Dispatch { public function dispatch(Request $request, Response $response = null) { $this->request = $request; if (!$response) { $response = new HttpResponse(); } $this->response = $response; ... if ($result->stopped()) { return $result->last(); } return $e->getResult(); } }
abstract class ActionController implements Dispatchable, InjectApplicationEvent, LocatorAware { use Stdlib\Dispatch; }
abstract class RestfulController implementsDispatchable, InjectApplicationEvent, LocatorAware { use Stdlib\Dispatch {Stdlib\Dispatch::dispatch as dispatchTrait};
public function dispatch(Request $request, Response $response = null) { if (!$request instanceof HttpRequest) { throw new \InvalidArgumentException('Expected an HTTP request'); } return $this->dispatchTrait($request, $response); } }}
Aliasing could potentially be a refactoring nightmare
namespace Zend\View;class TemplatePathStack implements TemplateResolver{ use Options { Options::setOptions as setConfig }}$templateStack = (new TemplatePathStack)->setConfig(['lfi_protection' => true]);
//$templateStack = (new TemplatePathStack)->setOptions(['lfi_protection' => true]);
Visibility
namespace Zend\View;class TemplatePathStack implements TemplateResolver{ use Options {Options::setOptions as protected}
public function __construct($options = array()) { $this->setOptions($options); }
}new TemplatePathStack(['lfi_protection' => true]);
Interface compliance
namespace Zend\View;
trait Options {
public function setOptions($options) {
//set the options
}
}
interface TemplateResolver {
public function setConfig($options);
}
class TemplatePathStack implements TemplateResolver {
use Options {Options::setOptions as setConfig;}
}
(new TemplatePathStack)->setConfig(['lfi_protection' => true]);
namespace Zend\View;trait Options { public function setOptions($options) { }}
class TemplatePathStack { use Options { Options::setOptions as setConfig; }}$templateStack = (new TemplatePathStack)->setConfig(['lfi_protection' => true]);var_dump($templateStack instanceof Options); // false
namespace Zend\View;trait Options { abstract public function setOptions($options);}
class TemplatePathStack { use Options; public function setOptions($options) { echo 'implementation enforced by trait'; }}$templateStack = (new TemplatePathStack)->setOptions(['lfi_protection' => true]);var_dump($templateStack instanceof Options); // false
namespace Zend\View;trait Options { abstract public function setOptions($options);}
class TemplatePathStack { use Options {Options::setOptions as setConfig;} public function setConfig($options) { echo 'implementation enforced by trait'; }}// Fatal error: Class Zend\View\TemplatePathStack contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (Zend\View\TemplatePathStack::setOptions)
Magic Constants<?phptrait Id { public function getClass() { echo __CLASS__; } public function getTrait() { echo __TRAIT__; } }
class Foo { use Id; }
(new Foo)->getClass(); //Foo(new Foo)->getTrait(); //Id
Autoloading<?phpnamespace NbeZf\Loader{ class foo { use Id; }}namespace { function __autoload($class) { var_dump($class); die(); }}// string(15) "NbeZf\Loader\Id"
Good things about traits
● Removes code duplication● Helps keeping your code cleaner● Maintainability● Aliassing works for interfaces● Property handling● Easy Autoloading
Bad things about traits
● Adds code complexity / understandability with the insteadof operator.
● Duck typing● Aliassing Fails for Abstract
Classes● Property handling
Benchmarks 5.3 vs 5.4● ZF project: http://nickbelhomme.com● Benchmark / Profiling tool: xhprof● How: 2 VirtualBox Machines - debian clones,
with only PHP upgrade (5.4.0RC3)
5.3.3
● Total Incl. Wall Time (microsec): 674,842 ms● Total Incl. CPU (microsecs): 672,042 ms● Total Incl. MemUse (bytes): 13,827,864 bytes● Total Incl. PeakMemUse (bytes): 13,865,976
bytes● Number of Function Calls: 14,524
5.4.0RC3
Total Incl. Wall Time (microsec): 166,813 ms● Total Incl. CPU (microsecs): 168,011 ms● Total Incl. MemUse (bytes):7,969,928 bytes● Total Incl. PeakMemUse (bytes):8,015,944
bytes● Number of Function Calls: 14,520
Who runs PHP5.4 today for testing?
YOU WIN
6
Please rate my talkhttps://joind.in/4774
The End!
THANK YOU
Slideshare, Twitter, IRC: NickBelhomme
http://blog.nickbelhomme.com
Please rate my talkhttps://joind.in/4774
Flickr Photo Credits● Helico● donkeyhotey● jannem● Thomas.constantin● SJ photography● davidagalvan● stev.ie● Mosman Council● DeaPeaJay