1

My goal was to be able to create an object which is composed by other objects without having to know beforehand what these objects were, then do checks to see if they actually exist, then add them to my collection and so on -- avoid all that fuss.

My use-case is that I have a Tooltip which I output to the markup. Now, this object is parsed by a higher-level Generator. This Generator looks inside the Tooltip and says "aha! you have a StyleInterface object (behavior), and so, I will include a CSS file for your" and so on. My goal is to allow people to add functionality to an object without enforcing anything. A friendly way, through code of saying "if you wanna add this, you can, but you don't have to."

And so, the Composite pattern was born.

I came up with it from here: Scaling inserting related optional objects to your collection

Yet, there is a problem that's appeared in this process: because it's PHP, I cannot type-hint that an array's members must be of a certain type, ComposableInterface in my case.

My pattern has achieved "syntactic sugar niceness", even if I honestly believe it's over-engineered and its goals were met:

  1. Signal to the outside world that it is dynamic: it is composed of a dynamic number of behaviors (objects).
  2. Make the insertion easy so you no longer have to name every single component in the constructor, as well as add it manually as in my original question.

Each item that can be inside of a Composite is called a Component and it must respect the following interface:

interface ComposableInterface
{
    public function getComposableName();
}

We identify it by getComposableName. Now, who ingests all these Composables? It's, as I said, the Composite:

interface CompositeInterface
{
    public function getComponents();
}

Let's build the Composite:

/**
 * An object which can be composed of multipel behaviors.
 */
class Composite implements CompositeInterface
{
    /**
     * @var array
     */
    private $components = [];

    public function __construct( $components = [] )
    {
        foreach( $components as $component ) {
            $this->components[$component->getComposableName()] = $component;
        }
    }

    /**
     * Retrieve the components.
     *
     * @return array
     */
    public function getComponents()
    {
        return $this->components;
    }
}

Let's try to use it by first creating some Composables components/behaviors that our main object can have:

interface Style extends ComposableInterface{}
/**
 * A component / behavior that can be part of a composite.
 */
class StyleOne implements Style
{
    public function getComposableName()
    {
        return 'style';
    }
}

interface Markup extends ComposableInterface{}
/**
 * A component / behavior that can be part of a composite.
 */
class MarkupOne implements Markup
{
    public function getComposableName()
    {
        return 'markup';
    }
}

interface Categories extends ComposableInterface{}
/**
 * A component / behavior that can be part of a composite.
 */
class CategoriesOne implements Categories
{
    public function getComposableName()
    {
        return 'categories';
    }
}

Sweet, we have 3 behaviors our Composite can have, let's try to initialize it:

$composite = new Composite([
    new StyleOne,
    new MarkupOne,
    //new CategoriesOne -- not needed, but it can be added!
]);

...and voila. This way, we can take away or add as many behaviors as we want.

So, what exactly did we do and are we over-engineering? I simply want to build a dynamic (in the numbers of behaviors) system that I can re-use and not have to check if a Composable exists every time I have to work with an entire Composite object.

With that in mind, we completely avoided having to type-hint our possible components and no longer have to manually check whether a Composable exists, then get its name, then add it to our collection.

It looks cleaner. But it still has that issue. How can I tell the system that inside my Composite, I want the array of arguments to be of type ComposableInterface without having to do a manual check on each component that's passed?

12
  • Anecdotally, it looks like you're trying to reinvent mixins or traits. Commented Jan 14, 2020 at 15:03
  • @RobertHarvey Could be, I'm dumb as a brick and I searched and searched and searched but nothing came up for PHP. Also, Traits are only bad because they are behaviors that cannot be taken away dynamically. A class' identity is directly tied to the trait it uses, where as my Composite is dynamic, not only that but these Composables are also interchangeable themselves. I am not sure what I created here, I just know that, performance aside, it's nice syntactic sugar that saves me a lot of time.
    – Daniel M
    Commented Jan 14, 2020 at 15:08
  • @RobertHarvey My goal was to avoid inheritance completely as, at least in this case, I believe it breaks reliability. I just read now on mixins and it seems that, indeed, the creators' intention was to also solve the issue of unneeded inheritance so that the behaviors (objects) in the container class can be fully reliable, without any side-effects.
    – Daniel M
    Commented Jan 14, 2020 at 15:16
  • you started off with the problem that you cant specify typed arrays and ended with the problem that you cant specify typed arrays
    – Ewan
    Commented Jan 14, 2020 at 15:17
  • @Ewan Which is exactly my problem in this question. The mixin talk was just very good talk and beneficial (to me at least). I would still like to fix my problem from the question, if it's fixed, then I test this "pattern" and see I don't like it, I can ask about mixin implementations for PHP. For now, the issue is type-hinted array members and / if they're a good idea.
    – Daniel M
    Commented Jan 14, 2020 at 15:18

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.