Exploring the Webiny Framework: The StdLib Component

Share this article

There’s an adage about a developer’s career cycle:

  1. Doesn’t use frameworks
  2. Discovers frameworks
  3. Writes his own framework
  4. Doesn’t use frameworks

Point 4, of course, referring to the newfound ability to use Composer to build your own framework from various battle tested third party components.

We all know there’s no shortage of frameworks in the PHP ecosystem, so it surprised me quite a bit to see another pop up rather recently.

The framework is called Webiny, and, while packed to the brim with wheel reinventions they deem necessary, there are some genuinely interesting components in there that warrant taking a look. In this introductory post, we won’t be focusing on the framework as a whole, but on the most basic of its components – the StdLib.

Webiny StdLib

No, not that “std”. StdLib stands for Standard Library and is at the core of every other sub-component of the framework. It’s kind of like adding a dependency or two to a random PHP project and before you know what’s happening, Symfony/Yaml is somehow already in there.

Among other things, StdLib, like many others before it, makes dealing with scalars significantly simpler by adding a fluent object oriented interface on top and some helper methods. For example, there’s a lightweight URLObject which contains some helper methods for dealing with redirection, schemes, ports, etc. Besides helping out with OO wrappers, the library also offers some basic validation, methods that help with building other Webiny components, a simple singleton trait, and more.

Where StdLib differs significantly, is in the fact that it’s implemented as a set of Traits – a vastly underused part of modern PHP development. For example, the aforementioned URLObject is instantiated like so: $this->url('https://www.webiny.com/');, because the StdObject trait is added to any class that needs this functionality. Most of the other components of the Webiny framework are implemented as Traits as well due to their isolated nature – the team chose this approach in order to simplify the hierarchy structure of classes, aiming for as few extends as possible.

StdLib is usable as a standalone package and can be cloned or “Composered” directly, or it can be pulled in as part of the entire framework. Let’s see what it offers, shall we?

Features

Internally, the StdLib consists of two sub-libs. One is the Exception set which is to be used only if you intend to build additional Webiny components (more on that in future tutorials). The other is the StdObject lib which contains the functionality we talked about before.

Aside from that, the library contains traits that utilize these sub-libs.

ComponentTrait

The ComponentTrait is only useful if you’re building additional Webiny components – something we won’t be dealing with just yet. For now, we’ll skip it.

FactoryLoaderTrait

The FactoryLoaderTrait is useful in summoning class instances that have been dynamically defined. For example:

$className = 'myNameSpace\myClass';
$argument1 = 'Hello';
$argument2 = 'World';
$mustBe = 'myNameSpace\mySubClass';

To instantiate myClass with the arguments “Hello” and “World” while making sure the instance is of the mySubClass type, you can take the following two approaches:

// standard PHP

try {
    $instance = new $className($argument1, $argument2);
    if (!($instance instanceof $mustBe)) {
        throw new Exception("Instances don't match!");
    }
} catch (Exception $e) {
    // Handle exception
}
// FactoryLoaderTrait approach

try {
    $instance = $this->factory($className, $mustBe, [$argument1, $argument2]);
} catch (Exception $e) {
    // Handle exception
}

As you can see, the second way is slightly shorter. Granted, the usefulness of this trait is questionable at best, especially considering that dynamic class names aren’t something one should use all that much, but when you take into account the level of standardization such a feature can bring across your entire organization/framework, the benefits become apparent. This trait’s approach is little more than a “coding standard” in dynamic class instantiation, but it can be a valuable one.

SingletonTrait

The SingletonTrait instantly turns your class into a singleton. There are extensive discussions all around the web about the “bad” nature of singletons – not just ancient scriptures like these but even some of our own old posts discuss this – but on the off chance you need one and can’t have a decent DI container implementation to use instead, it’s here.

One thing to keep in mind is the __construct conflicts. This particular trait doesn’t implement its own __construct method, meaning it can be used in both classes that have one of their own and those that don’t, unlike some other solutions, but it also means that you should adapt your class’ constructor to fit the singleton pattern if you choose to use this trait.

If you’re new to singletons, this post might help.

Another thing worth mentioning in this implementation is the fact that the trait implements a public init method and a protected _init method, executed in that order after the instance is created.

This is useful because you can use it as a post-creation initialization mechanism to further set up your singleton instance without having to rely on its constructor – maybe your class already has a defined constructor, and all you need is to turn it into a singleton, but the singleton mode of work needs some more tweaking? Perfect for init.

StdLibTrait

This trait is a combination of StdObjectTrait and ValidatorTrait. By itself, the StdLibTrait contains some json encode and decode helpers which are rather bare-bones right now, as well as a serialize and unserialize method which do essentially the same things as PHP’s counterparts. This may have been the place in which to instead use Symfony’s Serializer as a battle tested and multi-format supporting component.

ValidatorTrait

Starting with ValidatorTrait, it’s a simple collection of native PHP methods for checking type, but rewritten so they’re part of a class. I’m not sure of the reasoning behind this, as the API remains almost completely identical (self::isArray() vs is_array()), but I assume it has something to do with keeping the component extensible – being able to update these native methods without having to change the API lib-wide is guaranteed to be a priceless perk later on.

In one part, the validator makes use of the StdObjectWrapper which is a part of the StdObject sub library – it uses the Webiny OO wrappers for scalars and the url format to provide a fluent interface for checking these types.

StdObjectTrait

This is the core of the StdLib component, the main part. It provides the class that uses this trait with the ability to spawn ArrayObject, UrlObject, StringObject and DateTimeObject instances by means of an appropriate helper method.

StringObject

Arguably the simplest of the bunch, this object allows you to use strings as objects.

$string = $this->str("My string");

The instance will natively contain an encoding constant (defaulting to UTF8) and some helper methods like length, wordCount and subStringCount, all of which are demonstrably useful, as well as some native PHP functions once again wrapped into class methods. Via a ManipulatorTrait, common to all the StdObjects, the StringObject also has access to methods that change it – trim being the most familiar one but also featuring those for appending, prepending, adding slashes, and much, much more.

An enormous advantage of this approach is not only the ability to chain calls to the string object, but also the autocomplete such an interface provides for your IDE:

$string = $this->str("This is a string");
echo $string->hash()->padLeft(45, "testing");

In one line, we hash the string and pad the remaining character slots on the left of it with the word “testing” until there are 45 characters. The result is testif72017485fbf6423499baf9b240daa14f5f095a1. It hardly gets simpler than that, and it’s extremely readable.

As another example, see this one from the docs:

$string = new StringObject('Some test string.');
$string->caseUpper()->trimRight('.')->replace(' test'); // SOME STRING

Of course, the object implements the __toString method so that it’s directly usable as a string. Coincidentally, all other objects in the StdLib also implement this method, and are directly printable, producing the output you’d expect their non-object counterparts to generate.

The methods that are exposed are too numerous to name, so take a look at the file to find out about all of them.

ArrayObject

Similar to StringObject, the ArrayObject offers an easy interface to array manipulation. Naturally, it’s iteration-friendly so you can just loop through it as through any array with a foreach, behaving almost like a native array.

$array = new ArrayObject(['one', 'two', 'three']);
$array->first(); // StringObject 'one'
$array->append('four')->prepend('zero'); // ['zero', 'one', 'two', 'three', 'four']

Note that getting elements from this array format produces StdObjects, not actual scalars. The return value will always be of the StdObjectAbstract type. Strings produce StringObject, arrays produce ArrayObject, and numbers, curiously and somewhat both inconsistently and consistently produce an instance of StdObjectWrapper with the _value property set to the actual number. If the element is a class, that class gets wrapped with the wrapper, too. For example:

$array = $this->arr([1, "2", new myClass(1, 2)]);
var_dump($array->last());

This gives us:

I’m not sure how I feel about this. On one hand, this is super consistent, making sure we always get the StdObjectWrapper in some shape or form, but on the other, if I’m dealing with an array of classes, I couldn’t care less about this wrapper. There is, of course, a way to get the real value out of any of the Webiny StdLib objects – each has a val method which pulls out the underlying value contained within.

For all the various manipulative methods that arrays have access to by using this trait, see the manipulator. I find the chainable level syntax particularly useful – being able to go down into a multidimensional array by using key1.key2.key3 as an index is quite the impressive timesaver:

$array = [
            'k1' => 'test',
            'k2' => [
                'k3' => [
                    'k4' => 'deepest'
                ]
            ]
        ];
        
$a = new ArrayObject($array);
$a->keyNested('k2.k3.k4', 'webiny');
$a->keyNested('k2.k3.k5', 'anotherElement');

echo $a->keyNested('k2.k3.k4'); // webiny
echo $a->keyNested('k2.k3.k5'); // anotherElement

URLObject

The URL object is here to make dealing with URL syntax easier. It supports some common response codes, methods for dealing with query params, ports, and other variables and can also be manipulated easily – switch out scheme, hosts, domains etc with a single line of reliable code.

What’s strange is that URLObject doesn’t extend the StringObject. To me, extending would make sense seeing as they’re both, essentially, strings in this case and the former would benefit from the manipulative methods of the latter. There are also much better URL manipulation libraries out there, so I don’t see the benefit of using this one. One example that springs to mind is the excellent PHP League URL library which does all this and much, much more, with the added advantage of being fully tested and actively maintained by over a dozen of highly capable developers.

DateTimeObject

Finally, there’s the DateTimeObject. With helper methods bursting at the seams, the DateTime object provides a more fluent, natural interface to PHP’s native DateTime class and helps you deal with time-related problems:

$dt = new DateTimeObject('3 months ago');
echo $dt; // 2013-02-12 17:00:36
$dt->add('10 days')->sub('5 hours'); // 2013-02-22 12:00:36

In and of itself, the DateTimeObject trait is very handy if you’re already using StdLib in a project, but for any more complex DateTime manipulations, I would recommend Carbon with some Period added in for flavor.

That’s not to say the DateTimeObject doesn’t have its use – it supports default time zones, easy date format outputs of various shapes and sizes, simple and easy diffs as a lighter alternative to Period, human readable “X time ago” outputs, offsetting to different timezones, and much more.

Conclusion

By doing all this, the Webiny framework makes sure no developer coming into the project deals with native PHP types. They all have their appropriate wrappers and they all behave exactly as the developers want them to behave. Is this overly ambitious? Maybe. But I see it as an interesting approach akin to defining a coding standard before you start working on a project – the problems encountered during further stages of development will be domain specific exclusively, most likely saving time in the long run. The only big con I can see here is having to use the trait in every single class you want to give StdObject support to.

The Webiny project is an ambitious and interesting one. The scope of the framework is impressive and the ground its components cover vast – but whether or not they’re actually necessary, I’ll leave that up to you to decide. If you’re curious as to why I’m paying so much attention to this framework as to warrant a post (or several), it’s because I enjoy playing with new things, but also because I contributed to it a little bit and am curious as to where they can take it.

In some future posts, we’ll be looking at more Webiny components and even build a sample event driven application with the framework to take it for a proper spin and gauge its effectiveness. Until then, I encourage you to play around with its various aspects and tell us about your experiences.

Bruno SkvorcBruno Skvorc
View Author

Bruno is a blockchain developer and technical educator at the Web3 Foundation, the foundation that's building the next generation of the free people's internet. He runs two newsletters you should subscribe to if you're interested in Web3.0: Dot Leap covers ecosystem and tech development of Web3, and NFT Review covers the evolution of the non-fungible token (digital collectibles) ecosystem inside this emerging new web. His current passion project is RMRK.app, the most advanced NFT system in the world, which allows NFTs to own other NFTs, NFTs to react to emotion, NFTs to be governed democratically, and NFTs to be multiple things at once.

BrunoSframeworkFrameworksOOPHPPHPwebinywebiny-stdlib
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week