Back to Home

The programming interface "Fluid Interface" in PHP. A fresh look

php · aspect oriented programming · aop · pattern · fluent interface

The programming interface "Fluid Interface" in PHP. A fresh look


    When developing software, one of the important components is the high readability of the source code of the program. There are special techniques and recommendations that can improve the readability of the source code. One of the methods for improving the readability of the source code is the use of "fluid interfaces" (English Fluent Interface). We will talk about him in this article.


    Evolution. From simple to complex.

    I can assume that each programmer begins his journey as a PHP programmer by writing a banal application, "Hello, world!" After which there will be many years of learning the language and clumsy attempts to do something special: ORM / CMS / Framework (underline as necessary). I think everyone has that code, which is better not to show anyone. But this is an absolutely normal development process, because without understanding simple things you cannot understand complex ones! Therefore, let's repeat this way - we will start with simple examples and get to the implementation of the “fluid” interface as a separate class using AOP. Those who know this programming pattern in OOP - can safely go to the last part of the article, there you can get great food for thought.

    Let's get started with

    So that we do not have to go far for an example, let's take some user entity that has the properties of first name, last name, and password:

    class User
    {
        public $name;
        public $surname;
        public $password;
    }
    


    An excellent class that can be easily and elegantly used:

    $user = new User;
    $user->name     = 'John';
    $user->surname  = 'Doe';
    $user->password = 'root';
    


    However, it is easy to notice that we do not have any validation and you can make the password empty, which is not very good. In addition, it would be nice to know that field values ​​do not change without our knowledge (Immutable). These few considerations lead us to the idea that the properties should be protected or private, and access to them through a getter / setter pair. (note: this approach is precisely the basis of the Doctrine proxy classes)

    Said-done:

    class User
    {
        protected $name;
        protected $surname;
        protected $password;
        public function setName($name)
        {
            $this->name = $name;
        }
        public function setSurname($surname)
        {
            $this->surname = $surname;
        }
        public function setPassword($password)
        {
            if (!$password) {
                throw new InvalidArgumentException("Password shouldn't be empty");
            }
            $this->password = $password;
        }
    }
    


    For the new class, the configuration has changed a bit and now uses the setter method call:

    $user = new User;
    $user->setName('John');
    $user->setSurname('Doe');
    $user->setPassword('root');
    


    Nothing complicated, right? But what if we need to configure 20 properties? 30 properties? This code will be bombarded with setter calls and a constant appearance. $user->If the variable name is $superImportantUser, then the readability of the code will deteriorate even more. What can be done to get rid of copying this code?

    Fluid interface

    So, we come to the Fluent Interface programming template, which was invented by Eric Evans and Martin Fowler to increase the readability of the program source code by simplifying multiple calls to methods of one object. This is implemented using a chain of methods (Method Chaining), passing the context of the call to the next method in the chain. The context is the value returned by the method, and this value can be any object, including the current one.

    To be simpler, to implement a fluid interface, we need to return the current object in all setter methods:

    class User
    {
        protected $name;
        protected $surname;
        protected $password;
        public function setName($name)
        {
            $this->name = $name;
            return $this;
        }
        public function setSurname($surname)
        {
            $this->surname = $surname;
            return $this;
        }
        public function setPassword($password)
        {
            if (!$password) {
                throw new InvalidArgumentException("Password shouldn't be empty");
            }
            $this->password = $password;
            return $this;
        }
    }
    


    This approach will allow us to make a chain of calls, avoiding the plural name of the variable:

    $user = new User;
    $user->setName('John')->setSurname('Doe')->setPassword('root');
    


    As you already noticed, the configuration of the object now takes up less space and is easier to read. We have achieved our goal! At this point, many developers should ask: “So what? I already know this ... "Then try to answer the question:" What is the bad fluid interface in this form? "Before reading the next block of the article.

    So why is he bad?

    Perhaps you did not find the answer and decided to read it? ) Well then, go ahead! I hasten to reassure you: at the current level of OOP with a fluid interface, everything is fine. However, if you think about it, you will notice that it cannot be implemented as a separate class and connected to the desired object. This feature is expressed in the fact that it is necessary to affix monotonously return $thisat the end of each method. If we have a couple of dozens of classes with a couple of dozens of methods that we want to make “fluid”, then we have to manually deal with this unpleasant operation. This is the classic cross-cutting functionality.

    Let's finally make it with a separate class

    Since we have cross-cutting functionality, you need to go up a level above OOP and describe this pattern formally. The description is very simple - when calling public methods in a certain class, the object itself must be returned as the result of the method. In order not to get unexpected effects - let's make clarifications: public methods must be setters (start with set) and we will take classes only those that implement the FluentInterface marker interface.
    The final description of the “fluid” interface in our implementation in PHP will sound like this: when calling public setter methods that start on set and are in a class that implements the FluentInterface interface, it is necessary to return the object itself for which the call is made, provided that the original method returned nothing. Here is how! Now the only thing left is to describe it - we will describe it using the AOP code and the Go library! AOP:

    First of all, we will describe the interface marker of the "fluid" interface:

    /**
     * Fluent interface marker
     */
    interface FluentInterface
    {
    }
    


    And then the very logic of the "fluid" interface in the form of advice inside the aspect:

    use Go\Aop\Aspect;
    use Go\Aop\Intercept\MethodInvocation;
    use Go\Lang\Annotation\Around;
    class FluentInterfaceAspect implements Aspect
    {
        /**
         * Fluent interface advice
         *
         * @Around("within(FluentInterface+) && execution(public **->set*(*))")
         *
         * @param MethodInvocation $invocation
         * @return mixed|null|object
         */
        protected function aroundMethodExecution(MethodInvocation $invocation)
        {
            $result = $invocation->proceed();
            return $result!==null ? $result : $invocation->getThis();
        }
    }
    


    I’ll make a little clarification - the Around tip sets a hook “around” the original class method , fully responsible for whether it will be called and what result will be returned . It will look from the side as if we took the code of the method and slightly changed its code, adding our advice there. In the council code itself, we first call the original setter method and if it did not return anything to us, then we return the object itself as the result of calling the original method$invocation->getThis() . Here is such a simple implementation of this useful programming template in just a couple of lines.

    After all this, connecting a “fluid” interface to each specific application class is a simple and enjoyable job:

    class User implements FluentInterface
    {
        //...
        public function setName($name)
        {
            $this->name = $name;
        }    
    }
    


    All we need to use the now fluid interface in a particular class is to simply add an interface implements FluentInterface. There is no copying return $thisby hundreds of methods, only clean source code, an understandable interface marker and the very implementation of a “fluid” interface in the form of a simple aspect class. AOP will take on all the work.

    This article is for guidance only and is intended to reflect on the possibilities that can be accessed using AOP in PHP. I hope you were curious about the implementation of this programming pattern.

    References:
    1. Go! Aspect-Oriented Framework for PHP
    2. Wikipedia - Fluent Interface
    3. Github project

    Only registered users can participate in the survey. Please come in.

    Your opinion on the implementation of a fluid interface using AOP

    • 22.3% Wow! I didn’t know what could be done this way, I’ll try it at my leisure! 179
    • 34.1% The article is good, I wonder what else can be done with the help of AOP? 274
    • 31.6% I liked the article, I’ll take note, but so far it’s not time for AOP, I will focus on OOP. 254
    • 11.8% I did not like the article, everything is too opaque and incomprehensible, I will do it with pens in the old fashioned way. 95

    Read Next