PHP Traits
Presented by
Matthew Hellinger
Buzz.Tools Co-founder
What is a Trait?
 Set of methods used across multiple classes
 Reduces limitations of single inheritance
 Introduced in PHP 5.4
Example Trait
trait simpleTrait {
public function common1() {echo ‘1’;}
}
class simpleClass {
use simpleTrait;
}
$test = new simpleClass();
$test->common1();
Standard Use Case
trait EngineTools {
// Convert engine temperature to Celsius
function currentTempC() {
return ($this->engineTemp - 32) *
(5/9);
}
}
class Plane {
use EngineTools;
private $engineTemp;
function __construct() {
$this->startPlane();
}
private function startPlane() {
$this->engineTemp = 210;
}
private function fly() {
// Fly
}
}
class Car {
use EngineTools;
private $engineTemp;
function __construct() {
$this->startCar();
}
private function startCar() {
$this->engineTemp = 230;
}
private function drive() {
// Drive
}
}
$plane = new Plane();
$car = new Car();
echo $plane->currentTempC() . "<br/>";
echo $car->currentTempC() . "<br/>";
Why Traits Are Bad
 Potential namespace conflicts
 Trait changes alter multiple classes
 Methods spread outside class
Why Traits Are Good
 Avoids the “diamond problem”
 Simpler way to multiple inheritance
 Provides a new way to implement security*
Traits & Magic Methods
 Consider: __call & __callStatic
 Executed when a method is “inaccessible”
 Let’s build a new trait
Permissions Trait
// For an introduction to the concepts presented here, visit
// http://www.garfieldtech.com/blog/magical-php-call
trait PermissionsTrait {
private $FeatureContext; // Used to track feature this class is
associated with
private $FeatureACL; // Object reference to instantiated class
function __toString() {
// Return whether user has access to class
// return 'ALLOW' or 'DENY'
if ($this->FeatureACL->allowed_class($this->FeatureContext,
__CLASS__)) {
return 'ALLOW';
} else {
return 'DENY';
}
}
function __call($method, $args) {
// Die if user doesn't have permission to access method
// We could also log that this function was called
if (substr_count($method, 0, 1) == ‘_’) {
throw new Exception("Fatal error: Call to out of scope private
method " . __CLASS__ . "::{$method}()");
}
if ($this->FeatureACL->allowed_method($this-
>FeatureContext, __CLASS__, $method)) {
if (isset($this->{$method}) && is_callable($this->{$method}))
{
return call_user_func_array($this->{$method}, $args);
} else {
throw new Exception("Fatal error: Call to undefined
method " . __CLASS__ . "::{$method}()");
}
} else {
throw new Exception("Security error: Call to unauthorized
method " . __CLASS__ . "::{$method}()");
}
}
function __callStatic($method, $args) {
// Same code as __call, but with static references
// …
}
}
Need a Support Class
class featureACL
{
private $array; // private to prevent later modification
function __construct($featureList_array) {
$this->array = $featureList_array;
}
function allowed_feature($feature_name) {
return in_array($feature_name, $this->array);
}
function allowed_class($feature_name, $class_name) {
return array_key_exists($class_name, $this->array[$feature_name]);
}
function allowed_method($feature_name, $class_name, $method_name) {
if (isset($this->array[$feature_name][$class][$method])) {
return true;
} else {
return false;
}
}
}
Configure It All
// ------- CONFIG FILE START ----------------------------
// The following feature list would be defined in a
config file (for tracking in normal developer
// workflow... means features get mapped to
methods and classes in normal course of
development
$FeatureList = array();
$FeatureList['Feature1'] =
array('securedClass'=>array('method1',
'method2'));
$FeatureList['Feature2'] =
array('securedClass'=>array('method1',
'method3')); // overloaded
// ------- CONFIG FILE END ----------------------------
// Open DB connection and get feature list
accessible to client
$GroupFeatureList = array('Feature1', 'Feature2');
// All features client may access
$UserFeatureList = array('Feature2'); // All
features a specific user may access
// Note: the above is simulating the result of a DB
call getting back an array (add your own DB logic)
// We don't want user to have access to any
feature the parent group doesn't have access to
// so we compute intersection of the two arrays
prior to storing
$FeatureACL = new
featureACL(array_intersect($GroupFeatureList,
$UserFeatureList)); // No longer modifiable
unset($GroupFeatureList);
unset($UserFeatureList);
Create a Secure Class
class securedClass
{
// No need to duplicate the same permissions magic for every class:
use PermissionsTrait;
// By setting all methods private, they are inaccessible from outside class and therefore
// pass through the __call magic method (the permission gatekeeper)
private function method1($arg1, $arg2) {
return ($arg1 + $arg2);
}
private function method2($arg1, $arg2) {
return ($arg1 - $arg2);
}
private function method3($arg1, $arg2) {
return ($arg1 * $arg2);
}
// To protect methods from being called from outside the class by using accessPermissions,
// we prefix them with an underscore (_) a common convention for private method names
private function _internalMethod() {
// This can't be called from __call
}
}
Attempt to access
$secureFns = new securedClass();
// Attempt to execute a non-permissible method:
$result = $secureFns->method2(1,2); // Exception is
thrown
// These, however, would work:
$result = $secureFns->method1(1,2);
$result = $secureFns->method3(1,2);
What We Get
 Ability to lock down CODE execution to USER
 Ability to track EVERY method call
 Ability to track larger feature usage
 Self documenting functionality
Lessons Learned
 Traits are perfect for “universal” methods
 Namespace is an important consideration
 Look! We put magic methods to good use
Other Paths to Explore
 Method overloading (e.g. based on user type)
 Detailed logging (trace execution path)
 Dynamically generated methods
One Last Bit
 “insteadof” and “as” to avoid namespace conflicts
class myTools {
use screwDrivers, wrenches {
screwDrivers::returnTool insteadof wrenches;
wrenches::returnTool as returnWrench;
}
}
Go build something
secure!
Matthew Hellinger
matt@buzz.tools
linkedin.com/in/webspace

PHP Traits

  • 1.
    PHP Traits Presented by MatthewHellinger Buzz.Tools Co-founder
  • 2.
    What is aTrait?  Set of methods used across multiple classes  Reduces limitations of single inheritance  Introduced in PHP 5.4
  • 3.
    Example Trait trait simpleTrait{ public function common1() {echo ‘1’;} } class simpleClass { use simpleTrait; } $test = new simpleClass(); $test->common1();
  • 4.
    Standard Use Case traitEngineTools { // Convert engine temperature to Celsius function currentTempC() { return ($this->engineTemp - 32) * (5/9); } } class Plane { use EngineTools; private $engineTemp; function __construct() { $this->startPlane(); } private function startPlane() { $this->engineTemp = 210; } private function fly() { // Fly } } class Car { use EngineTools; private $engineTemp; function __construct() { $this->startCar(); } private function startCar() { $this->engineTemp = 230; } private function drive() { // Drive } } $plane = new Plane(); $car = new Car(); echo $plane->currentTempC() . "<br/>"; echo $car->currentTempC() . "<br/>";
  • 5.
    Why Traits AreBad  Potential namespace conflicts  Trait changes alter multiple classes  Methods spread outside class
  • 6.
    Why Traits AreGood  Avoids the “diamond problem”  Simpler way to multiple inheritance  Provides a new way to implement security*
  • 7.
    Traits & MagicMethods  Consider: __call & __callStatic  Executed when a method is “inaccessible”  Let’s build a new trait
  • 8.
    Permissions Trait // Foran introduction to the concepts presented here, visit // http://www.garfieldtech.com/blog/magical-php-call trait PermissionsTrait { private $FeatureContext; // Used to track feature this class is associated with private $FeatureACL; // Object reference to instantiated class function __toString() { // Return whether user has access to class // return 'ALLOW' or 'DENY' if ($this->FeatureACL->allowed_class($this->FeatureContext, __CLASS__)) { return 'ALLOW'; } else { return 'DENY'; } } function __call($method, $args) { // Die if user doesn't have permission to access method // We could also log that this function was called if (substr_count($method, 0, 1) == ‘_’) { throw new Exception("Fatal error: Call to out of scope private method " . __CLASS__ . "::{$method}()"); } if ($this->FeatureACL->allowed_method($this- >FeatureContext, __CLASS__, $method)) { if (isset($this->{$method}) && is_callable($this->{$method})) { return call_user_func_array($this->{$method}, $args); } else { throw new Exception("Fatal error: Call to undefined method " . __CLASS__ . "::{$method}()"); } } else { throw new Exception("Security error: Call to unauthorized method " . __CLASS__ . "::{$method}()"); } } function __callStatic($method, $args) { // Same code as __call, but with static references // … } }
  • 9.
    Need a SupportClass class featureACL { private $array; // private to prevent later modification function __construct($featureList_array) { $this->array = $featureList_array; } function allowed_feature($feature_name) { return in_array($feature_name, $this->array); } function allowed_class($feature_name, $class_name) { return array_key_exists($class_name, $this->array[$feature_name]); } function allowed_method($feature_name, $class_name, $method_name) { if (isset($this->array[$feature_name][$class][$method])) { return true; } else { return false; } } }
  • 10.
    Configure It All //------- CONFIG FILE START ---------------------------- // The following feature list would be defined in a config file (for tracking in normal developer // workflow... means features get mapped to methods and classes in normal course of development $FeatureList = array(); $FeatureList['Feature1'] = array('securedClass'=>array('method1', 'method2')); $FeatureList['Feature2'] = array('securedClass'=>array('method1', 'method3')); // overloaded // ------- CONFIG FILE END ---------------------------- // Open DB connection and get feature list accessible to client $GroupFeatureList = array('Feature1', 'Feature2'); // All features client may access $UserFeatureList = array('Feature2'); // All features a specific user may access // Note: the above is simulating the result of a DB call getting back an array (add your own DB logic) // We don't want user to have access to any feature the parent group doesn't have access to // so we compute intersection of the two arrays prior to storing $FeatureACL = new featureACL(array_intersect($GroupFeatureList, $UserFeatureList)); // No longer modifiable unset($GroupFeatureList); unset($UserFeatureList);
  • 11.
    Create a SecureClass class securedClass { // No need to duplicate the same permissions magic for every class: use PermissionsTrait; // By setting all methods private, they are inaccessible from outside class and therefore // pass through the __call magic method (the permission gatekeeper) private function method1($arg1, $arg2) { return ($arg1 + $arg2); } private function method2($arg1, $arg2) { return ($arg1 - $arg2); } private function method3($arg1, $arg2) { return ($arg1 * $arg2); } // To protect methods from being called from outside the class by using accessPermissions, // we prefix them with an underscore (_) a common convention for private method names private function _internalMethod() { // This can't be called from __call } }
  • 12.
    Attempt to access $secureFns= new securedClass(); // Attempt to execute a non-permissible method: $result = $secureFns->method2(1,2); // Exception is thrown // These, however, would work: $result = $secureFns->method1(1,2); $result = $secureFns->method3(1,2);
  • 13.
    What We Get Ability to lock down CODE execution to USER  Ability to track EVERY method call  Ability to track larger feature usage  Self documenting functionality
  • 14.
    Lessons Learned  Traitsare perfect for “universal” methods  Namespace is an important consideration  Look! We put magic methods to good use
  • 15.
    Other Paths toExplore  Method overloading (e.g. based on user type)  Detailed logging (trace execution path)  Dynamically generated methods
  • 16.
    One Last Bit “insteadof” and “as” to avoid namespace conflicts class myTools { use screwDrivers, wrenches { screwDrivers::returnTool insteadof wrenches; wrenches::returnTool as returnWrench; } }
  • 17.
    Go build something secure! MatthewHellinger matt@buzz.tools linkedin.com/in/webspace