PHP Traits

mattbuzz 288 views 17 slides Sep 10, 2015
Slide 1
Slide 1 of 17
Slide 1
1
Slide 2
2
Slide 3
3
Slide 4
4
Slide 5
5
Slide 6
6
Slide 7
7
Slide 8
8
Slide 9
9
Slide 10
10
Slide 11
11
Slide 12
12
Slide 13
13
Slide 14
14
Slide 15
15
Slide 16
16
Slide 17
17

About This Presentation

Novel use case scenario for traits enabling code-level user permissions


Slide Content

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 [email protected] linkedin.com/in/ webspace