PHP advanced feature - combination of Reflection and Factory design pattern [code example]

PHP advanced features - Reflection and the combination of factory design patterns [explained in combination with the example of laravel admin code]

Use reflection to realize the production of factory mode without creating specific factory classes

Article address http://janrs.com/?p=833 Reprint without authorization of the author

Please indicate the source of reprint

Reflection


JANRS.COM - PHP Reflection

What is Reflection

Reflection is reflection. Reflection provides object-oriented programming with the ability to introspect

This understanding is a little too conceptual. Generally speaking, it can find out the cause according to the result of the event. In programming, you can find out the class to which the object belongs and all the properties and methods of the class according to an instantiated object, and even read document comments. This process of backchecking is called reflection

PHP provides a complete reflection API and the ability to introspect classes, interfaces, functions, methods and extensions. In addition, the reflection API provides methods to extract document comments from functions, classes, and methods. See PHP official website for details Introduction to PHP reflection

What can Reflection do

As mentioned above, you can use reflection to obtain all properties and methods of a class, comment documents, and even access permissions [protected/private] of class properties and methods. These features greatly improve the flexibility of PHP. For example:

-The so-called elegance of the Laravel framework, namely container, dependency injection and IOC control inversion, is realized by these features
-Annotation routing in Hyperf framework is also implemented by obtaining annotations based on reflection
-Generate documents because reflection can obtain access to class properties and methods. You can scan all files of the whole project and then use reflection to generate documents
-Test driven development uses reflection to obtain the characteristics of all methods of this class for test driven development
-The development plug-in uses the reflection to obtain the internal structure of the class to realize the Hook function, such as the implementation of the framework plug-in

Advantages and disadvantages of Reflection

Advantage reflection provides anti parsing of classes, which obtains high flexibility compared with the original object-oriented programming method, and reasonable use can make the code look more elegant and concise. Originally, in object-oriented programming, when using an instance of a class, you need to create an object first and then use the method. However, the reflection mechanism is used. You only need to provide a method of the class, and then use the reflection mechanism to use the object or method. Laravel framework gained its reputation for elegance by using a lot of reflection. The implementation of annotation routing in Swoole's Hyperf framework also uses reflection

At the same time, because reflection is the anti process of class instantiation, it destroys the object-oriented encapsulation and directly exposes the whole internal structure of the class, which leads to the abuse of reflection, the code will be difficult to manage, the whole project will be very chaotic, and even the business execution will be disordered. Especially in a team of dozens of people on large projects, imagine that the original object-oriented only tells what can be used and what can not be used. CTO writes the underlying code and others inherit it and then use it. Others don't know the internal structure. Once reflection is used, if a programmer accidentally sets the originally protected or private properties or methods to be accessible, and other programmers call the hidden data or methods unknowingly, it will lead to unpredictable disasters [see the following example code]

Secondly, due to the high flexibility of reflection, it is impossible to trace the source by directly clicking on the code in the IDE. It is really painful for novices, as is the case with Laravel and Hyperf

In the following code, the reflection mechanism directly sets the private method to be externally accessible
#Example:

<?php
class Foo {
  private function myPrivateMethod() {
    return 7;
  }
}

$method = new ReflectionMethod('Foo', 'myPrivateMethod');

//This reflection function directly sets the method that was originally private permission to be accessible
$method->setAccessible(true);

echo $method->invoke(new Foo);
// echos "7"
?>

Factory design mode

Three factory design patterns [simple factory pattern] [factory pattern] [Abstract Factory pattern]

The simple factory pattern is also called the static factory method pattern. In short, the method of creating objects is implemented through a static method. In the simple factory pattern, instances of different classes are returned according to the parameters passed

In PHP, in the simple factory mode, there is an abstract product class [i.e. abstract class Calculate], which can be interface / abstract class / ordinary class. This abstract product class can derive multiple specific product classes, namely class CalculateAdd and class CalculateSub. Finally, a specific factory class [class CalculateFactory] is used to obtain the instance of the required product class

code implementation

1) Abstract product production class: Operation abstract class
//Production abstract class
abstract class Calculate{
    
    //Number A
    protected $number_a = null;
    
    //Number B
    protected $number_b = null;
    
    //Set number A
    public function setNumberA( $number ){
        $this->number_a = $number;
    }
    
    //Set number B
    public function setNumberB( $number ){
        $this->number_b = $number;
    }
    
    //Get number A
    public function getNumberA(){
        return $this->number_a;
    }
    
    //Get number B
    public function getNumberB(){
        return $this->number_b;
    }
    
    //Obtain the calculation results [obtain the produced products]
    public function getResult(){
        return null;
    }
}
2) Specific product categories: addition / subtraction, etc
//Addition operation
class CalculateAdd extends Calculate{
    
    //Obtain operation results [obtain specific products]
    public function getResult(){
        return $this->number_a + $this->number_b;
    }
}
//Subtraction operation
class CalculateSub extends Calculate{
    
    //Obtain operation results [obtain specific products]
    public function getResult(){
        return $this->number_a - $this->number_b;
    }
}
//Multiplication / division and other operations [other products]
3) Factory: factory. That is, a single class is used to create the instantiation process. This class is the factory. That is, the simple factory model
In PHP, the implementation method is actually a switch function or php8's new match function to instantiate the required product production class
//Instantiate different objects according to different operations
//[that is, instantiate the corresponding product class for production according to the required products]
//The corresponding implementation is actually a switch or php8 function and a new match function
//Let's demonstrate with the latest match function
class CalculateFactory{
    
    public static function setCalculate( $type = null ){
        return match( $type ){
            'add' => (function(){
                return new CalculateAdd();
            })(),
            'sub' => (function(){
                return new CalculateSub();
            })(),
            default => null;
        };
    }
    
}

//Specific use

$calculate = CalculateFactory::setCalculate('add');
$calculate->setNumberA = 1;
$calculate->setNumberB = 2;

//calculation
echo $calculate->getResult;//echo 3
Summary: the simple factory mode is actually to create a base class [abstract], which stores the common code of all specific production product classes, but does not execute the process. Then all specific production product classes inherit the base class and implement their own production processes. Finally, create a factory class, which is used to obtain the required production class according to the passed in parameters

Factory method mode is also called factory mode, which belongs to creative mode. In the factory mode, the parent class of the factory class is only responsible for defining the public interface and does not perform the actual production action. The actual production action is handed over to the subclass of the factory. In this way, the instantiation of the class is delayed to the subclass of the factory, and the instantiation of the specific product, that is, production, is completed through the subclass of the factory

In the factory mode, unlike the simple factory mode, there is an abstract factory class [interface CalculateFactory], which can be an interface / abstract class. This abstract factory class can derive multiple specific factory classes [FactoryAdd and FactorySub]

Code implementation [the following code needs to use the above production abstract class]

The following code needs to use the above production abstract class: abstract class Calculate
And specific production classes, i.e. CalculateAdd and CalculateSub. The implementation will not be repeated below

interface CalculateFactory{
    
    public function CreateCalculate();
    
}

class FactoryAdd implements CalculateFactory{
    
    public function CreateCalculate(){
        return new CalculateAdd();
    }
    
}

class FactorySub implements CalculateFactory{
    
    public function CreateCalculate(){
        return new CalculateSub();
    }
    
}

//Specific use

//Create factory instance
$calculateFactory = new FactoryAdd();
$add = $calculateFactory->CreateCalculate();
$add->setNumberA( 1 );
$add->setNumberB( 2 );

//calculation
echo $add->getResult();//echo 3
Summary: the difference between the factory mode and the simple factory mode is that in the simple factory mode, there is only one factory to produce the corresponding production object [CalculateFactory]. In the factory mode, each production object is produced by its own factory, and these factories inherit from the same interface [interface CalculateFactory]

Abstract factory pattern abstract factory pattern provides an interface to create a series of related or interdependent objects without specifying their specific classes. That's abstract. A popular explanation is that compared with the above factory mode, the abstract factory mode has a super factory on each different factory. This super factory is an abstract interface used to produce specific factories

In the abstract factory mode, there are multiple abstract product classes [i.e. abstract class Phone and abstract class Android], which can be interface / abstract class / ordinary class. Each abstract product class can derive multiple specific product classes [i.e. class IPhone / class MiPhone and class IOS / class Android]. An abstract factory class [i.e. interface AbstractFactory] can derive multiple specific factory classes [i.e. class iPhoneFactory and class MiFactory], and each specific factory class can create multiple instances of product classes [i.e. createPhone and createSystem]

code implementation

//Abstract product class
abstract class Phone{}
abstract class System{}

//Specific product categories
class IPhone extends Phone{}
class MiPhone extends Phone{}

//Specific product categories
class IOS extends System{}
class Android extends System{}

//Super factory
interface AbstractFactory{
    public function createPhone();
    public function createSystem();
}

//Specific Apple factory
class iPhoneFactory implements AbstractFactory{
    
    //Production of Apple phones
    public function createPhone(){
        return new IPhone();
    }
    
    //Apple production system
    public function createSystem(){
        return new IOS();
    }
}

//Specific millet factory
class MiFactory implements AbstractFactory{
    
    //Production of Xiaomi mobile phone
    public function createPhone(){
        return new MiPhone();
    }
    
    //Production Android system
    public function createSystem(){
        return new Android();
    }
}
Summary: compared with the factory pattern, the abstract factory pattern provides an interface to specify the products to be produced. Each factory inherited from this interface can produce according to the specified mode [AbstarctFactory in the code]

The above three factory modes are ultimately designed to extract duplicate codes, summarize them according to specific demand scenarios, decouple and reuse them, so that they can be used directly in the required scenarios

The three modes are summarized as follows:

Simple factory:

  • An abstract product class (which can be interface, abstract class and ordinary class) can derive multiple concrete product classes
  • A single specific factory class
  • Each specific factory class can only create one instance of a specific product class

    Factory mode:

  • An abstract product class (which can be interface, abstract class and ordinary class) can derive multiple concrete product classes
  • An abstract factory class (can be: interface, abstract class) can derive multiple concrete factory classes
  • Each specific factory class can only create one instance of a specific product class

    Abstract factory:

  • Multiple abstract product classes (can be: interface, abstract class, ordinary class), and each abstract product class can derive multiple specific product classes
  • An abstract factory class (can be: interface, abstract class) can derive multiple concrete factory classes
  • Each specific factory class can create multiple instances of specific product classes

Differences between the three modes:

  • The simple factory pattern has only one abstract product class and only one concrete factory class
  • The factory method pattern has only one abstract product class, while the abstract factory pattern has multiple abstract product classes
  • The concrete factory class of factory method pattern can only create one instance of concrete product class, while the abstract factory pattern can create multiple instances of concrete product class

Combination of factory mode and reflection

The reflection feature can be used to realize the production process of factory mode. An example is given in combination with larevel admin

Let's take a look at the following code. Requirement background: different permission buttons need to be displayed according to different roles

<?php
    
class TaskController extends BaseController
{
    use HasResourceActions;

    /**
     * Make a grid builder.
     *
     * @return Grid
     */
    protected function grid()
    {
            //Grid Columns...
        
            if (Admin::user()->inRoles([AdminUserModel::getAssignmentRole()])) {
                $grid->disableBatchActions();
                $grid->disableEditButton();
                $grid->disableCreateButton();
                $grid->disableDeleteButton();
            } elseif (Admin::user()->inRoles([AdminUserModel::getEvaluatorRole()])) {
                $grid->disableBatchActions();
                $grid->disableEditButton();
                $grid->disableCreateButton();
                $grid->disableDeleteButton();
                $grid->actions(function (Grid\Displayers\Actions $actions) {
                    $actions->append(new ConfirmCloseTaskAction());
                });
            } else {
                $grid->disableCreateButton();
                $grid->disableDeleteButton();
                $grid->disableEditButton();
                $grid->disableBatchActions();
                $grid->disableViewButton();
                $grid->disableActions();
            }
    }
}

The above code is obviously bloated at a glance. With the increase of business [i.e. the increase of Controller] and roles, more repeated judgments and repeated codes need to be written

Solution: different roles need different permissions. Each role can use a fixed method to set permissions. This fixed method can set permissions for different roles. These conditions just meet the usage scenario of factory mode: namely:

  • Abstract a product class to derive permission product classes of multiple roles
  • Abstract a factory class to derive multiple specific factory classes, which are represented as the scene corresponding to the permission button to be used
  • Each specific factory can create multiple specific product classes (i.e. instantiate permission products of multiple roles) in the scenario of using permission buttons

The code is as follows [in the following code, reflection will be used instead of factory production]

1) Abstract a product class to derive permission product classes of multiple roles
<?php

namespace App\GridActionFactory;

use Dcat\Admin\Grid;

/**
 * Factory interface
 */
interface GridActionInterface
{
    //Permission of operator role
    function salesmanAction(Grid $grid);
    //Assign permissions to the role of administrator
    function assignmentAction(Grid $grid);
    //Authority of financial role
    function financeAction(Grid $grid);
    
    //... permissions for other roles
}
2,3) the two steps are included together. Abstract a factory class to derive multiple specific factory classes, which are represented as the scene corresponding to the permission button to be used. Among them, the setRoleAction method uses reflection to directly produce, that is, it replaces the process of creating an instance of each specific factory class
<?php

namespace App\GridActionFactory;

use Dcat\Admin\Admin;
use Dcat\Admin\Grid;

/**
 * Set Action permission abstract class
 */
abstract class GridActionAbstract
{
    //
    abstract public static function setAction(Grid $grid, string $role);

    /**
     * Filter role
     *
     * @param string $role
     * @return bool
     */
    protected static function isInRoles(string $role): bool
    {
        return Admin::user()->inRoles([$role]);
    }

    /**
     * Call the corresponding method
     * [This method is actually a factory in the factory mode, which is specially produced]
     * [Multiple factories correspond to each Controller that needs Action permission]
     * [Each Controller produces its own Action permissions]
     * [This production is achieved through reflection]
     *
     * @param Grid $grid
     * @param string $role
     * @param string $class
     * @throws \ReflectionException
     */
    protected static function setRoleAction(Grid $grid, string $role, string $class)
    {
        $r = new \ReflectionClass($class);

        $methodName = $role . 'Action';
        if (!$r->hasMethod($methodName))
            throw new \Exception('Method Not Found [ method : ' . $methodName . ' ] ');

        $method = $r->getMethod($methodName);
        $method->invoke($r->newInstance(), $grid);
    }
}

According to the above reflection to implement the instantiation process, the permissions of the above TaskController can be simplified into the following code:

<?php

namespace App\GridActionFactory;

use Dcat\Admin\Grid;

class TaskAction extends GridActionAbstract implements GridActionInterface
{
    /**
     * @param Grid $grid
     * @param string $role
     * @throws \ReflectionException
     */
    public static function setAction(Grid $grid, string $role)
    {
        if (!parent::isInRoles($role)) return;
        
        //The production process is directly realized by calling setRoleAction of the parent class
        parent::setRoleAction($grid, $role, self::class);
    }

    //There are roles that require permission buttons under TaskController
    //Assignor role
    public function assignmentAction(Grid $grid)
    {
        //Permission button
        $grid->showActions();
        $grid->showViewButton();
    }

    //There are roles that require permission buttons under TaskController
    //Financial role
    public function financeAction(Grid $grid)
    {
        $grid->showActions();
        $grid->showViewButton();
    }

    //There are roles that require permission buttons under TaskController
    //Salesman role
    public function salesmanAction(Grid $grid)
    {
    }
    
    //... other roles
}

After encapsulation with design patterns, the code for controlling permissions in the TaskController above is directly optimized as follows: [much more elegant ~]

<?php
    
class TaskController extends BaseController
{
    use HasResourceActions;

    /**
     * Make a grid builder.
     *
     * @return Grid
     */
    protected function grid()
    {
            //Grid Columns...
            //Financial role button
              TaskAction::setAction($grid, AdminUserModel::getFinanceRole());
            //Assignor role button
              TaskAction::setAction($grid, AdminUserModel::getAssignmentRole());
        
            //... other role buttons
            /*
            if (Admin::user()->inRoles([AdminUserModel::getAssignmentRole()])) {
                $grid->disableBatchActions();
                $grid->disableEditButton();
                $grid->disableCreateButton();
                $grid->disableDeleteButton();
            } elseif (Admin::user()->inRoles([AdminUserModel::getEvaluatorRole()])) {
                $grid->disableBatchActions();
                $grid->disableEditButton();
                $grid->disableCreateButton();
                $grid->disableDeleteButton();
                $grid->actions(function (Grid\Displayers\Actions $actions) {
                    $actions->append(new ConfirmCloseTaskAction());
                });
            } else {
                $grid->disableCreateButton();
                $grid->disableDeleteButton();
                $grid->disableEditButton();
                $grid->disableBatchActions();
                $grid->disableViewButton();
                $grid->disableActions();
            }
            */
    }
}

Summary: design patterns and reflection are often used when writing frameworks. However, in the project, the appropriate use of design patterns and reflection can make the code more robust and extensible, and it is also very elegant ~

Welcome to my blog Yang Jianyong's personal blog http://janrs.com

Keywords: PHP Design Pattern reflection

Added by renegade33 on Thu, 18 Nov 2021 05:14:57 +0200