📜 ⬆️ ⬇️

Programming template "Flowing Interface" in PHP. A fresh look


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


Evolution. From simple to complex.

I can assume that each programmer starts his way to 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 the appropriate). I think everyone has that code, which is better not to show anyone. But this is an absolutely normal development process, because without an understanding of simple things one cannot understand complicated ones! Therefore, let's repeat this path - let's 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 move on to the last part of the article, there you can get excellent food for thought.

Let's get started

So that we don’t have to go far beyond the example, let's take a certain user entity that has the properties of a first name, a last name, and a password:
')
class User { public $name; public $surname; public $password; } 


Excellent class that can be used easily and gracefully:

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


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

No sooner said than 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 call to setters:

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


It seems nothing complicated, right? What if we need to configure 20 properties? 30 properties? This code will be filled with calls to setters and the constant appearance of $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 pattern, 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 a single object. This is accomplished with the help of 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 simple, to implement a fluid interface, we need to return the current object in all the setters 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 multiple indication of the variable name:

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


As you have already noticed, the configuration of the object now takes up less space and is much easier to read. We have reached the goal! At this point, many developers should have the question: “So what? I already know this ... ”Then try to answer the question:“ What is the bad flowing 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 can see that it cannot be implemented as a separate class and connected to the desired object. This feature is expressed in the fact that you have to monotonously put return $this at 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 end-to-end functionality.

Let's finally do it with a separate class.

Since we have end-to-end functionality, we need to rise to a level above the PLO and describe this pattern formally. The description is quite simple - when calling public methods in a certain class, you must return the object itself as the result of the method. In order not to get unexpected effects, let's clarify: public methods must be setters (start with set) and we will take classes only those that implement the interface marker FluentInterface.
The final description of the “flowing” interface in our PHP implementation will be: when calling public setters that start with set and are in a class that implements the FluentInterface interface, you must return the object for which you call, as a result of calling the method provided that the original method did not return anything. Here is how! Now it remains the case for the small - we will describe it using the AOP code and the Go library! AOP:

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

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


And then the logic of the “flowing” interface itself is in the form of a council 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 will make a small explanation - the Around advice sets the 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 outside as if we took the method code and slightly changed its code, adding our advice to it. 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 $invocation->getThis() object as the result of calling the original method . Here is such a simple implementation of this useful programming pattern just a couple of lines.

After all this, connecting a “flowing” interface to each specific class of application is a simple and pleasant job:

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


All we need to use the fluid interface in a specific class is to simply add the interface — implements FluentInterface . No copying of return $this for hundreds of methods, only pure source code, a clear interface marker and the implementation of the fluid interface itself as a simple aspect class. All work will take on AOP.

This article is for informational purposes only and is intended solely for thinking about the possibilities that can be accessed using AOP in PHP. I hope you were interested in learning about the implementation of this programming pattern.

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

Source: https://habr.com/ru/post/170019/


All Articles