A synopsis of the Suphle Framework

A synopsis of the Suphle Framework

·

11 min read

Introduction

Hello there. In this post, we will be demonstrating some functionalities of the Suphle framework by drawing parallels between the objectives constituting its blueprint, and implementations intended to alleviate code smells that hamper project maintainability. Suphle is a new PHP framework released recently, for building sophisticated full-stack software. Suphle is most suitable for business-critical database-driven applications that will be actively maintained over time.

For readers not abreast with PHP's development in recent years, permit me to dispel the following misconceptions regarding the language:

  • With the aid of a load-balancer and no extensions, the PHP processes created by the Suphle server are no longer destroyed after handling each request. Application instances are re-used just as those built in other server-side languages. The project responsible for this is called Roadrunner. It equally grants us access to functionality previously absent in the language e.g. Websockets.

  • Suphle bundles with Psalm, a library for detecting and correcting type-related errors at compile/server-build time.

  • PHP is not synonymous with SQL injections and procedural functions. Majority of Suphle's functionality dwells in instantiable classes. There are zero procedural-style functions in Suphle. These classes are auto-wired for you by Suphle's Container. A plethora of ORMs exist to discourage you from exposure to SQL injection risks by minimizing direct interaction with queries.

Suphle's command for bootstrapping resource management is not marketed as a revolutionary facility, since such convenience was already possible more than a decade ago when DHH presented Rails to the world. The industry has not stagnated in that time; as such, new tools are expected to improve on that model. Despite domain uniqueness from business to business, some patterns have emerged as non-negotiable in the journey of the web's evolution:

  • An API

  • Fidelity on one or more client interfaces

Various technologies have struggled to assert their presence in every user-facing software published. From serious ones like real-time communication, to trivial ones like GDPR dialogs. Developers within this epoch start out practicing fundamentals such as retrieving input values, exchanging them for database objects, and formatting them into shapes expected by the consumer. Unfortunately for them, these rudimentary etiquettes turn out grossly inadequate under real-life work conditions. Most enterprise frameworks will arm its developer with the constructs for meeting these needs, but leave their usage to the developer's discretion. This is perhaps Suphle's most significant difference from them -- it's like a code review that complains about anything whose absence is dangerous.

In addition to this core drive, Suphle has the following over-arching objectives:

  • Breaking business logic into silos for better management between multiple maintainers. A non-existent architecture is potentially a haphazard one, making it difficult for newer collaborators to maintain the project in the absence of the original author.

  • Enforcing recommended code patterns that encourage cohesion, DRY, testability.

  • Minimizing application failure to the barest minimum.

Based on both my experience and extensive research, it is evident that certain practices are indispensable for applications that fall into the niche described earlier. There are dedicated chapters on the documentation that go into detail regarding the solutions Suphle offers for circumventing undesirable circumstances. However, diving into the tome has reportedly been overwhelming. So in this post, we will skim over an overview of them.

Business logic architecture

Modules

Perhaps the most visible update in the structure of applications built by immigrating developers is that Suphle brings native support of the tried and proven Modular-monolith concept to PHP Frameworks. It's a progression from code bases with a Controllers folder full of Controllers catering to diverse resources. Aside from the potential of relevant modules being scaled into their own microservices, this structure is most advantageous in scenarios where resources entail more than basic CRUD queries. Resources that grow to this size, or those important enough to the business, may necessitate limited interference by assigning one maintainer to it, whose development and testing is designated to an environment as close as possible to isolation (even when they need to depend or communicate with each other).

Below, a Suphle application's entry point is shown to expose two modules for HTTP request handling:


namespace AllModules;

use Suphle\Modules\ModuleHandlerIdentifier;

use Suphle\Hydration\Container;

use AllModules\{ModuleOne\Meta\ModuleOneDescriptor, ModuleTwo\Meta\ModuleTwoDescriptor};

class PublishedModules extends ModuleHandlerIdentifier {

    function getModules():array {

        return [
            new ModuleOneDescriptor(new Container),

            new ModuleTwoDescriptor(new Container)
        ];
    }
}

This is your application's outskirts, superseding the intra-module route collections.

Enforced dependency prohibition

For its high flexibility, Suphle has this as one of its most opinionated aspects. Developers fall for the temptation to perform all sorts of operations within their Controllers, at the detriment of future maintainability. For this reason, Suphle's controllers are sanitized during the application's build phase. Detected violations will halt the server build. Following this divergence from the traditional motive of controllers, it makes sense to redefine them into what we call Service-coordinators.

Mandatory integrations

Both frameworks and their accompanying libraries make provision for bolstering their programs with recommended integrations. Unfortunately, these aren't apparent or glaring to all users of the framework. These integrations cut across components of back-end engineering ranging from:

  1. Not centralizing things, to,

  2. Leaving them for later -- one of the technical debts that never gets paid down the line.

  3. Erasing type-related errors.

In concrete terms, the following components are affected:

  • Image upload: The object for interacting with uploaded images is designed in such a way that further evaluation of the request fails if the image is stored without further optimization.

  • Request validation: As an incentive for all validatable endpoints to be decorated by a set of rules, the Framework will terminate request handling if such endpoints are missing validation rules.

  • Static-type errors: Psalm is bundled with the server build command in order to either indicate or correct type-related errors in a dynamically-typed language; thereby elevating PHP to a sphere previously occupied by only TypeScript.

  • Database transactions: Endpoints mutating the database in a non-trivial way i.e. executing multiple disparate queries should be wrapped by a transaction. Suphle provides decorators that abstract away the low-level details such as appropriate lock strategy, error management, protecting update integrity, etc, across one or more modules.

  • Data modelling: Modifications to the connected ORM force all inheriting models to be composed of migrations and factories, encouraging seeding through tests rather than a DBMS.

Violations to these rules are expected to be caught during development/testing, once the routes are being visited.

Evolution-friendly design

These are semantics geared towards constructing software that is better open for not only development co-location, but more robust implementation of business requirements.

  • Authorization: Model-based authorizers promotes authorization-based business rules from the scope of services, into the over-arching model layer itself; leaving the services to handle other computations without fear of manual adherence to those rules.

  • Conditional factory: This a construct for pooling conditional business rules into an OOP umbrella, abstracting it away for reuse and testability.

  • Input reader: This builds upon the existing convention of hydrating ORM instances out of route pattern placeholders, both granting greater flexibility in query customization to the caller and restricting database fields retrieved.

  • Events: Suphle is emphatic in its recommendation to use events, narrowly avoiding an enforcement of their usage. Their binding API is designed to only tolerate assignment of all handlers of events from an emitter to one listening class. It also makes special provision for listening to events emitted by neighboring modules.

A realistic event bind and emitting flow is shown below:


use Suphle\Events\EventManager;

class AssignListeners extends EventManager {

    public function registerListeners ():void {

        $this->local(CheckoutCart::class, CartReactor::class)

        ->on(CheckoutCart::EMPTIED_CART, "handleEmptied" ); // triggers CartReactor->handleEmptied with any payload sent below
    }
}

#[InterceptsCalls(SystemModelEdit::class)]
#[VariableDependencies([

    "setPayloadStorage", "setPlaceholderStorage"
])]
class CheckoutCart extends UpdatefulService implements SystemModelEdit {

    use BaseErrorCatcherService, EmitProxy;

    public const EMPTIED_CART = "cart_empty";

    public function __construct (private readonly Events $eventManager) {

        //
    }

    public function updateModels () {

        $this->cartBuilder->products()->decrement("quantity");

        $this->emitHelper(self::EMPTIED_CART, $this->cartBuilder);

        return $this->cartBuilder->delete();
    }
}
  • Outbound requests: Abstract HTTP requests out of the business and services department such that response can be statically represented to the caller, tested, and consumed safe from errors.

Infallible requests

Automated testing is one of software's most important facets. This is why each chapter of the documentation is punctuated with a section describing how to test the component it discusses, multiple chapters of the Appendix are dedicated to the subject, in addition to the main Testing chapter. In spite of these, it's inevitable for requests to fail in production. When that occurs, instead of jeopardizing the entire request, writing exception details to a log and waiting for the developer to attend to it, Suphle's services are engineered to not only isolate points of failure, but, with the aid of a Bugsnag configuration, report the emergency through a queued job.

Services insured by this wrapper merely need one decorator to activate it, while the caller now has to check the status of the object before consuming its result.

Suppose we have some Coordinator fetching data from the sources service1Result and service2Result in order to complete its request, a volatile service2Result can be reliably executed as follows:


$response = compact("service1Result");

$service2Result = $this->throwableService->getValue();

if ($this->throwableService->matchesErrorMethod("getValue"))

    $service2Result = $this->otherSource->alternateValue(); // perform some valid action

$response["service2Result"] = $service2Result;

return $response;

Even without the check $this->throwableService->matchesErrorMethod("getValue"), a failure of the preceding statement will not disrupt the successful response nodes, as long as they are independent of each other.

As you may have guessed, this is most useful to SSR applications where content is fetched and rendered in one go rather than tiny requests to diverse endpoints.

Some dessert to go with your Suphle

We have discussed features whose usage the Framework vehemently considers critical. In addition to them, Suphle introduces new constructs you are unlikely to have seen anywhere else:

  • Flows: This is an experimental performance booster for optimistically loading various kinds of responses before they are being made. Front-end frameworks already do this but no back-end framework currently preserves outgoing response, extracts nodes out of it, visits them using developer-defined rules, caches the result, serves and invalidates the cache with the intricacies relating to resource originator.

  • Bridge: This is intended to facilitate onboarding existing web-based projects written in other PHP frameworks, instead of recreating them from scratch in Suphle.

  • Route collection utilities: Aside their traditional responsibility of translating incoming requests into complementary Coordinators, Suphle's route collection brings numerous infra-level equipments to the doorstep of the average developer. First-class support for versioned routes is currently optional.

An API can be derived from the existing browser route collections. The browser routes default to your API's v1, but can also be overridden by more specific API patterns. If this sounds familiar to the concept of Content-Negotiation, it's because they are remotely similar. However, Route-mirroring differs ever so slightly: Content-Negotiation is often implemented as a middleware that simply replaces the response format. It's inadequate for applications that require entirely different content on specific paths, for example between the mobile and web versions.

Suphle's route collections also bring closer the concept of canary requests for A/B, internal, and temporary feature releases. Canary collections are like custom route collections for defining any conditions for determining what actual route collection the Router should use in serving the request.

Catching up with the web's evolution

So, where does Suphle stand within the current development standards? DHH has repeatedly proven to be a futurist ahead of his time. One of his great gifts to the development world is a handy library that brings fidelity to server-rendered software, for a fraction of the complexity or replicating that using front-end frameworks.

Suphle leverages these libraries for its presentation layer, entirely obviating the need for an API or dedicated front-end developers.

Summary

Some have argued that not all applications require this level of meticulousness, and that is correct. For the sort of applications that derive little to no benefit from the optimizations and introductions discussed here, the framework will be an overkill. It's neither something to be ashamed of nor hate the Framework for.

This also means that Suphle is not intended for those who belong to the framework-agnostic school of thought. If you will spend half the time struggling to build the software without the installed framework --in so doing, discarding the very benefits it aims to provide-- that's neither beneficial nor productive.

cup-of-tea

However, do beware of prevention being better than cure. An anecdote to buttress this admonition may suffice: An important document I was writing recently, got overwritten when I unwittingly edited it in between my devices. All updates made on my laptop were lost when the phone resumed where I left off and synced with the server by sending its present contents.

Since it's neither a collaborative nor version-controlled program, syncing local copies kind of makes sense, under the assumption that changes may have been made while the device was offline. Suphle on the other hand, averts this tragedy using one of the decorators discussed earlier, specifically, MultiUserModelEdit, discussed at length in its appropriate section. Ideally, the user should have been informed of a newer version by terminating the synchronization, regardless of whether it's a collaborative or version-controlled software. Thus, it may not be entirely wise to wait until you find a use case for all components mentioned here.

Most applications are essentially CRUD, but even among them, there are those whose endpoints hardly make simplistic queries. They entail the computation of, sometimes, frequently changing business rules. A lot of thought ought to go into their implementation instead of simply going with the flow. Applications that fall into this bracket may find integrating Suphle's implementations painstaking. However, Suphle's mission is to stand in as a disciplinarian, not to make the developer's life more difficult. Its utilities should enlighten and equip us with conventions and adequate tooling for improving the quality of our programs.