Traits and conflicts

--

Conflicting traits

PHP supports Traits, and like in many other programming languages they can be stacked and composed. In a scenario where one class uses two different traits with the same method signatures, conflicts may arise.

Sample problem

In the domain of our main application here at Sharesquare, we had a working class for one kind of model, let’s call it Blue, and another one for a different kind, let’s call it Red. When it was time to introduce Magenta, which was a combination of Red and Blue with the Red nature prevailing whenever a single choice had to be made, we thought about making two traits out of these classes and defining three separate color classes. However, when Magenta had to use both the Red and Blue traits, that's when hell started to break loose.

Which pill, the blue or the red one? Image credits: Pixabay.

How

Let’s consider the following traits: Blue.php, Red.php.

namespace App\Traits;

trait Blue
{
public function greeting()
{
echo "Hello Github.\nBlue Here.";
}
}
namespace App\Traits;

trait Red
{
public function greeting()
{
echo "Hello Github.\nRed Here.";
}
}

Now let’s combine these two traits and introduce the new Magenta class. At the same time, we will try and solve the collision problem, a problem that arises due the fact that the same method signature name is shared from both Blue and Red:

class Magenta
{
use Red, Blue {
Red::greeting as protected redGreeting;
Blue::greeting as protected blueGreeting;
}

public function greetingFromBlue()
{
$this->blueGreeting();
// override here...
}

public function greetingFromRed()
{
$this->redGreeting();
// override here...
}

public function greeting()
{
$this->greetingFromBlue();
echo "\n";
$this->greetingFromRed();
}
}
Different image, same question. Image credits: Pixabay.

Here, we have used different aliases for both traits, which will allow us to differentiate which trait method to call:

use Red, Blue {
Red::greeting as protected redGreeting;
Blue::greeting as protected blueGreeting;
}

We can use the local methods of the Magenta class to override trait functionality or use combined functionality by defining local methods.

Usage

$magenta = new Magenta();

$magenta->greetingFromBlue();
$magenta->greetingFromRed();
$magenta->greeting();

Sample services

Naming overrides as a solution just works, but defining aliases can be a bit messy, especially if we have a long list of methods in the traits. Let’s explore a better approach, this time pushing forward a little theory with a couple of services.

We’ll continue with the same sample problem.

How

Our first service, just to keep the naming simple (at the cost of a little less fantasy) will be: RedService.php

php

namespace App\Http\Services;

class RedService
{
public function greeting()
{
echo "Hello Github.\nRed Here.";
}
}

And, of course: BlueService.php

php

namespace App\Http\Services;

class BlueService
{
public function greeting()
{
echo "Hello Github.\nBlue Here.";
}
}

Now, let’s introduce the new Magenta::class class, which combines the flavors of the blue and red pill services:

class Magenta
{
protected $red;
protected $blue;

public function __construct()
{
$this->red = new RedService();
$this->blue = new BlueService();
}

public function __call($functionName, $arg = [])
{
$pillType = $arg[0] ?? null;
switch ($pillType) {
case 'red':
$this->red->{$functionName}();
break;

case 'blue':
$this->blue->{$functionName}();
break;

default:
// combine - use both
$this->red->{$functionName}();
$this->blue->{$functionName}();
break;
}
}
}

Usage

Let’s test it live:

$new = new Magenta();

$new->greeting('red');
$new->greeting('blue');
$new->greeting();

In this approach, we have used the magic function __call($functionName, $arg = []) inside the Magenta class. It accepts the first parameter as the function name and the second parameter can be any arguments that we want to pass to this function.

These magic methods provide flexibility and control over the behavior of objects in PHP. By implementing these methods in your classes, you can define custom actions for various object interactions and events. Here, we are using __call($method, $arguments), which is invoked when an inaccessible or non-existent method is called on an object. It allows us to handle and respond to undefined method calls. For more information, refer to the official PHP documentation on __call magic method in PHP.

In a completely different language and paradigm, the poison pill is a thing. Image credits: danilo.alvesd on Unsplash.

Thoughts?

Using the above magic methods can make our class much cleaner, and you can call any methods on Magenta from the red or blue service just like you call methods on original class methods. You can also override functions by declaring a local method inside the Magenta class for future calls to the relevant pill method.

Room for improvement

This approach provides much cleaner code, but adding proper validation or exception handling could make it even better. For example, let’s assume some of the methods are not common between both pills. Here’s how we can improve it:

public function initials()
{
$this->redMethods = get_class_methods($this->red);
$this->blueMethods = get_class_methods($this->blue);
}

Then, check if the methods exist for both pills:

if (!in_array($this->redMethods, array_keys($this->functions))) {
throw new BadMethodCallException();
}

Some more elegance

With PHP 8.3, an #[\Override] modifier is introduced for functions: would you improve the snippets above, thanks to this classic override statement? And if yes, how?

No worry if you have no answer yet, perhaps we will give you ours in a future blog post!

Blog by Riccardo Vincelli and Usama Liaquat brought to you by the engineering team at Sharesquare.

--

--

Sharesquare.co engineering blog by R. Vincelli
Sharesquare.co engineering blog by R. Vincelli

Written by Sharesquare.co engineering blog by R. Vincelli

This is the Sharesquare.co engineering blog, brought to you by Riccardo Vincelli, CTO at Sharesquare. Real-life engineering tales from the crypt!

No responses yet