phparchitect-2024-02
phparchitect-2024-02
Also Inside:
'
a php[architect] print edition
Every month, we here at php[archi- Package.’ Wallen demystifies the Event Write For Us
tect], as well as an extended group of package, providing a comprehensive If you would like to contribute,
fantastic PHP community members guide on its installation and integration contact us, and one of our editors
from around the world, work very hard into your applications. In a somewhat will be happy to help you hone
to deliver a magazine that will help equip unconventional take, Eric Mann’s Secu- your idea and turn it into a beauti-
you with not just the knowledge but also rity Corner encourages a strategy where ful article for our magazine.
the perspective needed to navigate the ‘Cheating is Encouraged’. Mann advocates
Visit https://phpa.me/write
complexities of managing projects and for using OWASP cheat sheets as a crit-
or contact our editorial team
day to day coding. As the PHP landscape ical resource in a developer’s toolkit. In
at [email protected] and get
evolves, staying informed and adaptable an industry where technology outpaces
started!
is key. We hope this issue serves as both a memory, these cheat sheets serve as an
compass and a map, guiding you through invaluable reference, ensuring developers
the challenges and opportunities that lie can access crucial security practices Stay in Touch
ahead in PHP development. precisely when they’re needed. Don't miss out on conference,
To start things off, our feature article, Edward Barnard’s 2500 Feet column book, and special announcments.
‘An Overview of Domain Driven Design’ presents ‘Transitional Implementation, Make sure you're connected with
by Rob Allen, dives into Domain-Driven Part 1’, emphasizing the significance us.
Design (DDD), which offers a compre- of understanding the process before • Subscribe to our list:
hensive approach to understanding and diving into code. Barnard’s insights on https://phpa.me/sub-to-updates
addressing complex business needs in documenting the transitional steps in
• Mastodon:
software development. Jack Peterson architecture development are enlight-
@[email protected]
follows that up in his feature, ’Domain ening and a testament to the meticulous
Driven Design Is Great; But Don’t Stress planning required in scalable software • Twitter: @phparch
About It,’ which brings a refreshing development. Last month in Education • Facebook:
perspective on balancing innovation Station, Chris Tankersly introduced us http://facebook.com/phparch
with practicality. It’s a reminder that to Symfony UX. He’s back at it again,
while frameworks and design principles expanding even more in this month’s
guide us, flexibility and creativity in their article, ‘Symfony UX: Part 2’. Chris
application can lead to truly remarkable elaborates on using additional Symfony
solutions. packages in an effort to reduce the
Steve McDougall’s API Guy column JavaScript code that needs to be written.
on ‘API Essentials: A Developer’s Guide You’ll read about Twig Components, Live
to Authentication, Validation, and DTOs’ Components, Turbo, and Turbo Frame.
walks you through the fundamental role Chris sets us up to continue learning
of HTTP requests in the daily tasks of more next month, with a focus on Turbo
a developer, emphasizing the handling, Streams.
saving, validating, and fetching of data. In Maxwell Ivey’s column Barrier-Free
Oscar Merida’s PHP Puzzles dives into Bytes ‘Using Different Browsers’, Max
‘Five Card Stud’ and establishes a founda- highlights the common oversight in
tional understanding of valid poker hands, website accessibility, attributing it not
leveraging unit testing with PHPUnit for to malice but to a lack of awareness or
controlled input and detailed analysis. constraints like time and budget. In her
On Matt Lantz’s Radar is a ‘A Compar- thought-provoking column, “Dear Past
ative Analysis of Swoole vs Roadrunner’ Me, What were you thinking?” Beth
and the different ways to make fast PHP Tucker-Long listens to the voices of the
even faster. The benefits and drawbacks past, also known as code comments, and
of these two solutions. Christopher Mill- the familiar frustration of deciphering
er’s musings on code documentation one’s own perplexing code from the past.
further enrich this issue, providing both Thank you for joining us on this
technical knowledge and philosoph- journey. Your dedication to learning and
ical reflections on our practices in his growth is what drives the PHP commu-
Readable Code column Is ‘Your Code nity forward. We hope all of you will join Download the Code
Documented Enough To Help?’ us in Chicago in April, and if you do, be
Archive:
Frank Wallen’s PSR Pickup column sure to introduce yourself to your other
takes us through the practical applica- PHP friends. https://phpa.me/February2024_code
tions of ’PSRs In Action: PHP League Event
FEATURE
API or dealing with a message queue the problem we’re solvingfrom the point
Introduction while ensuring the codebase is under- of view of the people whose problem it
Software development is hard. Oh, it’s standable and maintainable. Domain is and understand it. I can’t stress this
easy to write a throwaway app. Just bash complexity comes from the simple part enough: the software serves the
some code together, and you’re done. fact that every business process looks problem, and as developers, we need
That doesn’t work so well for a long- simple from the outside but invariably to understand that problem to be part
lived professional app with multiple has several exceptions and side effects of the solution; otherwise, our software
people working on it. We need to design when studied in detail. DDD is all about hinders rather than helps.
our application to solve the immediate reconciling this complexity and deliv- Our goal is to design a model of the
problem and be maintainable and flex- ering successful software that works. problem and how we’re going to solve
ible as the needs of the business change. So, what is DDD? At its simplest, it. The problem space is known as the
Domain-driven design is one way to do Domain-Driven Design provides a domain, and so we call this thedomain
this, and we will explore it in this article. framework for the software design model, which is a bit jargon-y but at
Domain-Driven Design, known based on the concepts of creating a least has the benefit of being easy to
as DDD, was first introduced by Eric model of the problem space and using refer to and clear about what it is. “The
Evans in his 2003 book “Domain- an agreed common language for it. people whose problem it is and under-
Driven Design: Tackling Complexity in Interestingly, it is not about the specifics stand it” is a bit of a mouthful, so we
the Heart of Software”. It is a fascinating of the technology used but is all about can use a shorter term here, too. As the
book and provides a set of practices for gaining knowledge about the problem people who understand the problem
building software using a collaborative space that the software operates in and are the experts in that problem, we call
approach from the perspective of the applying that to the software. themdomain experts.
business. I’ve used the termthe busi- There are two facets to Domain- To create the domain model, All we
nessa couple of times now. This is the Driven Design: strategic design and need to do is get the knowledge about
organization for whom the software tactical design. Strategic design is where the domain from the domain experts
is built. It means much more than just we are concerned with understanding into the heads of the developers. This
that, though. It’s a shorthand way to the system’s high-level structure and requirescommunication. Communi-
encompass the entire context in which organization to create the software’s cation is not a bad word, but it isn’t
our software is developed, including fundamental architecture. Tactical something that developers are known
the organization’s processes, its objec- design is where we deal with the to be comfortable with. Sorry, but
tives, and the users of our software. We detailed design decisions that impact the best developers & architects are
also call this the domain of our software. the way we write our code, focusing on the ones who can communicate. If I
The key to understanding Domain- maintainability and flexibility. Simply could give any developer one piece of
Driven Design is in the sub-head put, strategic design is the big picture, advice, it would be to practice the art of
of Evan’s book:Tackling Complexity and tactical design is the nitty-gritty communication. Nothing will further
in the Heart of Software. Software is specifics. your career more than this.
complex mainly because we mix tech-
Domain Models
nical complexity (how to do it) with
domain complexity (what to do). With
Strategic Design Creating the domain model is abso-
technical complexity, we need to deal For me, the really interesting part lutely the key part of DDD. Humans
with practical things like talking to an of DDD is at the strategic level. This is solve problems by creating models
where we develop our understanding of
of how the world works in order to value is. For the other parts of the app, found that linking to it from every rele-
understand it. These simplifications of you can use simpler designs or third- vant repository’s README is helpful
reality provide the key details we need party packages or services as you do not for developers, along with a link to it
to understand something for our use need to add USP value there and will from the business people’s key systems,
case. For example, we don’t need to benefit from their design work. which helps ensure that it stays active.
know the full workings of the internal You should do whatever works best for
combustion engine and transmission to
understand how to drive a car. We build
A Ubiquitous Language your organization and project.
Ubiquitous language is not just a
a model with an engine that goes faster As I have mentioned, to create our glossary, though. It is the language of
when we push the accelerator, and that model, we need to communicate with the project. This means that we use
engine causes the wheels to go around domain experts and translate their these wordseverywhere -in diagrams on
when we put the car’s gearbox in drive, explanations into a model of the struc- whiteboards, UML diagrams, explan-
which causes the car to go forward. The ture and operations of the problem atory documents, and the source code
details about ignition timing, fuel-air space. This is most easily done by of the application itself. The language is
mix, differentials, clutches, gearboxes, talking, and as with every group of also changing over time as more knowl-
and so on are not part of our model of humans, there’s jargon in the problem edge becomes available (it’s surprising
driving a car as they don’t matter for this space, and words have specific mean- how often an aside in a conversation
use-case. For a mechanic who needs to ings in specific contexts. For example, adds new meanings and nuances) along
fix a car, those details do matter, and the the word “risk” means something with new use cases, and the software
model of the car is different. different to an insurance underwriter grows. In these cases, update diagrams
So it is with our software application. than someone in finance. and documentation and refactor source
The domain model represents a view Using the same words with the same code to use the new words. Obsess over
of the problem space from the point meaning everywhere is vital if the this as a mismatch in words in the soft-
of view of the use cases we need in our software is to succeed. Domain-driven ware compared to the words used in the
software. We leave out all the bits that design introduces the concept ofubiq- project is very costly and painful over
do not matter, even though they are uitous languageto describe this shared the lifetime of an application.
important to other situations and use language. When talking with domain
experts, clarify the meaning of each
cases within the business organization.
It is an abstraction of reality, not a special word they use, such as “risk” Creating the Domain
documentation of it. when talking with insurers or “plan” Model
Creating the model is where the when talking with air traffic controllers.
We may think we know what the word The process of creating a domain
rubber meets the road. It’s hard to model is as mundane as it is important.
create a good model, and the first ideas means, but there is always a level of
nuance that the experts know that we It’s just talking and drawing diagrams
are likely not the best. Try multiple coupled with explanatory notes. There’s
ideas and iterations until they reflect don’t. I’ve found that the best way to
get this information is using conversa- no special format or software required,
the problem space well enough that and it’s generally easier to be tactile and
everyone involved understands it. A tions in real-time, preferably in person
if possible. Nothing beats being able to use a whiteboard and sticky notes if you
model is not static. You do not create can. The lack of perceived permanence
it, store it in Google Docs, and forget ask clarifying questions in the moment.
At its core, the ubiquitous language gives everyone permission to modify
about it. It evolves with new under- the picture or move steps around in a
standing and new business cases. There is a set of words that describe things
and processes. It usually makes sense to process during conversations.
are multiple expressions of it: usually We call this collaborative process-
documents, diagrams, and, of course, create a business glossary for all these
terms. It’s not unusual for different knowledge crunching. It involves
source code. All expressions use the discussions, brainstorming, and
same language, the language of the domain experts to have slightly different
meanings for a given term, so the glos- questioning to explore and refine the
domain. model of the problem domain, with
Focus your effort where it matters. sary serves to help clarify the meaning
between different experts, too. It doesn’t each exploration giving us additional
Not all of the systems need to be insights and understanding. The goal
designed using DDD, as it usually isn’t matter where the glossary is stored as
long as it is where everyone can find is to find and reflect on the intricacies
cost-effective to do so. Identify the core of the real-world domain we’re repre-
domain and complexity to focus on and and maintain it. That means a mark-
down file in a git repository is unlikely senting so we don’t miss something
apply DDD. This makes sense if you important in our implementation.
think about it as the core domain, and to be a good place as it risks developers
becoming gatekeepers to it. Similarly, a Usually, I start with an outline entity
its complexity is the reason to write the relationship diagram, as shown in
software in the first place; it’s where the Word document that someone emails
around is also a poor choice. I have Figure 1. These can be formal or quite
informal, as per this example where we write down the enti- within the business. Within a bounded context, we can ensure
ties involved in a passenger booking a travel ticket. The biggest that the language is consistent and specific, such as “lead” for
advantage of an informal diagram is that it lends itself to the sales, “customer” when purchasing, and “insured” when they
whiteboard and easy collaboration with the domain experts are claiming.
who can just as easily add a box without the intimidation of Bounded contexts protect the domain model and should
UML. not leak implementation details. From the software’s point
of view, we compose bounded contexts into applications,
Figure 1. where each bounded context is authoritative within its space,
owning the UI, business logic, and persistence code. For
simpler applications, you may only have one context, but as
an application grows, introducing bounded contexts ensures
that each one retains integrity and can be changed as required
independently from the other contexts in the application. The
bounded context is autonomous, and this helps to prevent the
software from descending into aBig Ball of Mudarchitecture.
Once we have more than one context in play, we need a
new diagram to draw them together. This is called thecontext
map. Again, there’s no formality here. You just need a picture
that explains how all the contexts fit together. It’s common to
add the non-core contexts, too, that simpler designs and third
parties. This way, we have a full overview of the system. These
From this, we need to work out what processes are required aren’t supposed to be complicated things, as you can see by
to make this happen. One way to do this collaboration effi- the example in Figure 2:
ciently is to useevent storming. This is a workshop-based
methodology initially developed by Alberto Brandolini in Figure 2.
2012. This is a workshop attended by both developers (to ask
questions) along with domain experts and product owners
(to answer them). This workshop concentrates on the domain
events that are to be mapped into the software. When we talk
about domain events, we mean the things that happen that
result in processes being performed. For example, “Customer
chose an insurance policy”. Various concepts are attached to
the event, such as the actor (person executing the event) and
business process (what happens as a result of this event). For
full details on event storing, theEvent Storming presentation-
given by Brandolini at DDD Europe 2019 is a good place to
start.
Whichever way you choose to explore the problem space
and the requirements of the software, the result is a model In this example, we have the three contexts of Marketing,
that usually looks like a collection of diagrams that is then Sales, and Booking, where we note the links between domain
implemented in the software. models, such as a quote leads to a payment of that a subscriber
in the marketing context is a lead in Sales. As most work is not
Managing a Big, Complex Domain done in a vacuum, this map has a Legacy context to hold the
parts of the application that exist and aren’t part of this work.
For larger software projects or projects that are long-lived,
the model will be complex and large, becoming more so.
There is a need to manage this complexity; otherwise, the Tactical Design
model will lose integrity and be less explicit. This can usually The tactical design is the set of technical resources that are
be spotted when language becomes ambiguous, with different used in the construction of the domain model within the
meanings in different places. application. The overall intention of your tactical design is
To manage this, we divide the model into separatebounded to add clarity to the implementation of the domain model,
concepts. I like this term, and it’s quite clear what it is. A keeping it maintainable and flexible. However, some common
bounded context is a model that is understood in a specific tactical design patterns are worth considering when imple-
context. For example, an insurance domain model may be menting the model in software.
made up of a sales context, a purchasing context, and a claims Firstly, ensure that we have a separation of concerns.
context representing the different lifecycles of a customer One area to consider is reducing dependencies between
components. This can be achieved with a traditional layered We can build upon this by thinking about our domain logic.
or hexagonal architecture and event-driven and Command If the business process within the model operates on multiple
Query Responsibility Segregation (CQRS) structures. entities, it can be useful to put it into a separate class with a
Regardless of the choice, intentional separation of concerns name that explains what it does. This is known as intention-re-
and management of dependencies is tactical design work. I vealing interfaces and seems incredibly obvious until you
tend to start with a layered design approach and will intro- deal with a codebase that doesn’t have clearly-named classes,
duce event-driven or CQRS when it becomes obvious that the interfaces and traits. Again, we come back to how important
implementation would benefit from them. I’m not much of a ubiquitous language is for understanding. Similarly, we want
top-down designer and prefer to iterate. I cannot emphasize clarity with our process logic so our methods and functions
enough that being comfortable with refactoring (along with do not have any side effects as this allows for flexibility when
having good tests) is the secret to a well-maintained applica- adding to the domain model as new functionality is required.
tion that lasts for the long term. As you can probably tell, tactical design in DDD can be
We talked about Ubiquitous Language earlier, and to boiled down to ensuring clarity of the code with the language
my mind, one of the most important things you can do to of the domain and that any item within the codebase should
make your code better relates to naming things. Yes, one of not couple two different concepts in the domain model. The
the hardest parts of software development! It is important specifics of how to do this are up to you.
to ensure that the implementation of the model follows the
language of the domain. Name your classes, methods, and
variables per the ubiquitous language you have developed.
Conclusion
Don’t call the class User if the domain calls it Customer, for The majority of this article isn’t about the software design
instance. Similarly, it can be helpful to use the common terms patterns in DDD and their implementation. This is inten-
we use for different types of classes to improve communica- tional as DDD is technically agnostic, so implementation is
tion and understanding between developers and, again, help open to innovation. Evans introduced specific concepts, such
to ensure that we maintain separation of concerns at the as a layered architecture with plain domain entity objects that
object level. use separate repository classes to deal with persistence. This
A particularly useful concept is entities and value objects. is not a bible to be slavishly followed, though, and if your
Entities are objects defined by their identity. That is, they have preferred PHP framework doesn’t work that way, then imple-
properties that describe their uniqueness, and one instance ment your model within that framework’s norms. Leverage
cannot be substituted with another. One example is that other patterns, such as value objects, as they make sense for
two Passenger instances represent different specific people your implementation.
and so cannot be used interchangeably. This contrasts with The most important thing about DDD is its strategic
value objects with no concept of identity; one instance is the design. The key benefit of this approach is the collaboration
same as any other. An example would be a Money object, as with the subject matter experts so that the software devel-
two different Money objects with the same value of $5 can be opers and architects understand the business processes
substituted with one another. Describing the properties of the properly and can communicate their implementation back.
domain model in terms of entities and value objects exposes This requires a common language, and Evans’s concept of
important design considerations around immutability and ubiquitous language coupled with this collaborative design
persistence and is a worthwhile exercise. It’s very common to process makes for much better software that’s more flexible to
extract value objects from our entities, as in the example of changing business needs.
Figure 3, where we can extract the address from the Passenger
entity into its own Address value object.
Rob Allen is a software consultant and
Figure 3. developer with many years experience in a
variety of interesting languages. He’s partic-
ularly interested in APIs and the ecosystem
around them along and contributes to Slim
Framework, rst2pdf & Apache OpenWhisk
along with other open source projects. Rob
is a published author and based in the UK
where he runs @akrabat
did a bit of advertising, too, to try to get right people. Mix in a little bit of Lean visual interfaces needed to be written
randomly sampled users. Startup into that, and you probably for each platform.
But the data would consistently have a good chance of success (of ship- If this resonates with your project …
show that our app wasn’t getting much ping the ‘right’ thing, at least). Smaller there is hope!
traction or much-repeated engage- feedback cycles increase the probability Most PHP projects can fit into one of
ment. Latency and flat-out wrong or of success on every project I’ve worked these four categories:
misleading data from third-party APIs on. And I agree with this approach. • A full-stack web application.
were a deal-breaker in the real world. Where things went sideways in my • An API (Probably RESTful).
When you’re in the aisle, and you have project with respect to DDD is all at the • A Plugin (Think WordPress).
a kid tugging on your arm, they don’t tactical level. • A function packaged as an app
care that the API hasn’t responded yet. Tactical DDD is all about the details (Cloud Function, CLI command,
They want to know that you saved the of the code and how it ought to be worker/task, etc).
thing that they wanted (and you did so written: Factories, Entities, Aggregate Of those possibilities, only two
to avoid a temper tantrum). If the item Roots, Repositories, and other design of those really even make sense for
was saved, it wasn’t “No” from Mom or patterns. Add in some CQRS and Event discussing DDD: A full-stack web
Dad on the item; it was a “Maybe.” Sourcing too. Lots of verbiage and application and an API. But let’s walk
I eventually realized that the project concepts, no doubt. through the other two, even if they
was like pursuing fusion energy – The indecision and ambiguity that don’t fit the discussion topic.
always a little bit away, regularly came about when trying to implement For command-line tasks or “Cloud
encouraged by well-meaning friends DDD concepts were palpable when Functions”: Full Stop. It’s a small
that they ‘liked the changes.’ It kept providing updates to the team on prog- single-purpose command. Add your
feeling close enough to keep going but ress. I tried to fit a square peg into a Guzzle, Add your PDO, Add your
far enough that it needed more time, round hole and kept getting stuck on Redis. Whatever you need to do …
more re-designs, and a lot more brain- the right way to shape and structure the it’s supposed to do one thing. Keep
power to work as well in reality as the entities, repositories, and services and it simple and focused. You might not
idea did in our heads. figuring out what the aggregates were. even need Composer or any of those
Eventually, we ran out of steam (and All while simultaneously trying to distill dependencies. Fewer lines of code =
personal budget). We realized that there and implement real-world feedback Fewer bugs.
were fundamental business risks that from end-users of the app. The entities For plugins, again, full-stop. Just
we should’ve thought about in advance. never felt quite ‘right.’ And, by golly, just use the underlying framework’s way of
We had an over-reliance on third-party querying and pushing data sure was persisting and fetching data and move
APIs — What if Amazon just cut off simpler – but no, I was determined to on with life. Plugins aren’t intended to
our API access? (they did: not enough figure out what these gurus were talking be super complex; if you’re building the
revenue-referring traffic). As a team, about and do it the ‘right way.’ next WooCommerce … you may be
everyone was getting frustrated with The fatal flaw I had as an engineer I the exception. Granted. For the rest of
the velocity of development. Frankly, made was believing that I needed to us … we’re not going to fix the plugin
we should’ve picked just one platform write my code in a more structured, ecosystem with cleaner code than other
to test a comprehensive version of the more maintainable way. I wrongly plugins with services, nice domain
idea. “But the idea was good,” and believed that most of my problems were models, and so on. The ecosystem
“everyone likes it.” So, we proceeded with the frameworks and language I has documented ‘best practices’ and
with complete hubris and confidence chose, and yes, that “I just didn’t under- common ways of doing what you need
on all platforms. Change friction across stand Entities and the nuances with the to do. And, if your plugin is successful
the three platforms became a real pain. ORM.” and you want to hire someone else to
We eventually closed the business. Instead of shipping a working app maintain it, they have to learn not only
Nice story. Another startup that faster, we spent an inordinate amount about your idea but also your weird
failed along the way in the heaps of of time refactoring components that implementation choices. That’s going
failed startups out there. But what does didn’t need to be refactored. And it to cost you extra. Just keep your design
that have to do with domain-driven wasn’t just the backend; we did the same with the norm.
design? DDD has two tiers that most thing on the front-end with framework For APIs: Okay, now we might have
people discuss: Strategic and Tactical. migrations from Angular to React, and some complexity here. Are you using a
My version of Strategic DDD is this: on the iOS side with language updates framework like Laravel, or the Word-
get everyone on the same page to figure from Objective C to Swift. With each Press API, or Laminas? If so, patterns
out what terms mean what and in what definitional change, a visual change exist on how to write an app. Decisions
context so that you can be sure that meant API changes, which meant new have been made by the maintainers
you’re building the right thing for the client code for the mobile apps. New so that you don’t have to. Are they the
best decisions? Maybe, maybe not. Data design complexity in most projects any other new programming language,
access patterns like Row-Data-Gateway tends to be around the one thing that tool, or framework that’s promoted
and Table-Data-Gateway, for example, nobody is ever satisfied with – what’s in to programmers. Specifically, I found
have some performance issues… but the report and how it’s designed. that the tactical implementation of an
it’ll probably work just fine up to a Full-Stack Web Applications: Let’s Aggregate Root, how to persist value
certain ‘scale.’ So don’t worry about it go old-school here and ignore, for the objects, the proper design of an Entity,
(for now)! Scale alone is often not a suffi- sake of simplicity, running an API + as well as where the proper place to
cient reason for most PHP projects to SPA (Single Page Application). No, this have a repository in an app does not
become extra clever, and in this context, app is all PHP + a little bit of HTML + align with most of the frameworks and
I’m talking scale in terms of request CSS, and some JavaScript to add those the default implementations available.
throughput or total traffic needing to be swirly twirly animations that make I found myself trying to implement
served – scale as in computational and everyone feel good in life. So, what these new concepts and struggling
IO needs. An additional index being does a full-stack application need to do through the “how does this all fit
added probably is all that’s actually (sequentially)? together” realities. Questions came up
needed to bring a poorly performing 1. A request is made. like, “How do I persist a collection of
page to a reasonably performing page value objects in my entity?” The ORM
2. Back-end stuff happens (Valida-
most of the time. wants to store this in a 1:Many kind of
tion, Data handling, Database
If architecture is what’s done in the way, and that means they get IDs since
calls, and API calls).
presence of constraints, then consider they are actually just another table with
that you can always throw more servers 3. A response is returned. a foreign key relationship. “Should these
behind a load balancer, increase CPU be lazily loaded or eagerly fetched?”
Step two is where things get inter-
count, allocate more RAM, and add Should these “Value Objects” be part
esting. Regardless of the framework you
more or faster storage. You can add of a collection .. or should I actually
choose, they each have a set of conven-
caching with Redis and you can verti- just make another ‘entity’ since that’s
tions for how step 2 should be handled.
cally scale that database for quite some kind of how it’s fetched anyway? “If I
Similar to a plugin, no tactical DDD
time until you actually need to make update the aggregate root … does that
needs to be done most of the time.
“real engineering decisions”. mean that all of the value objects need
Just follow the patterns laid out by the
At that point in time, your project to be re-saved (and thus also get new
framework authors. Write the control-
ideally is so successful that you can IDs)?” That seems awfully un-perfor-
lers. Add the models. Query them. As
hire someone else to solve that for you. mant to me. And that’s going to break
you work on your project, you’ll learn
Recall earlier my team’s beef about how we reference the objects on AWS
where things get weird with the frame-
‘business-oriented decisions’ over ‘engi- S3 anyway. What seemed to work well
work you chose. The good news is that
neering-oriented decisions’? Yeah, in one spot then felt really awkward in
because it’s software, you can always
there’s a reason those businesses are another section of code. Especially as
change how it behaves. You’re not
successful. They regularly choose to the “ubiquitous language” shifted and
forced to stay within the patterns of
prioritize their customers’ needs and we gained more clarity on what the
that framework. If you don’t like how
solve the actual problem that needs to goals really were.
queries are dynamically constructed,
be solved. Customers don’t care about ARGH! … The problem wasn’t so
fine, just inject in your own query class
programming languages, frameworks, much with DDD itself. The problem was
or otherwise. Implement your solution
DDD, or the topics that concern most that there was an impedance mismatch
and move on; you’ve solved the problem
programmers. They want a solution at a philosophical level between DDD
for the day, and that’s good enough.
that works right, the first time, and with and the design decisions of the frame-
If only I understood that more clearly
as little fuss as possible. Good enough works I was using. And that can create
then! We would’ve identified and
architecture is what works in the real a lot of mental friction – it certainly did
understood the business risks much
world. “Optimal” or “best” should for me.
sooner than we did (and cut the project
mostly be reserved for the philosophers This impedance mismatch leads to a
off much earlier). We were so focused
of the day to debate. failure of the “five beers test”, a software
on being technically optimal and using
A good 80+% of the APIs I’ve person- design philosophy that can be summa-
the project as our own personal learning
ally worked with at companies big and rized as follows: “If you’re paged at 3
playground that we failed to have clarity
small can be distilled down to CRUD A.M. and you’re five beers in and don’t
of the real problems that we needed to
(Create, Read, Update, Delete) + third- understand what’s going on with the
solve — business risks, idea risks, and
party dependencies (e.g., another API) code then it’s too complex”. This is the
scope creep.
+ notifications (either to people or heart of how I now approach designing
I take issue with DDD’s apparently
another service). Sometimes, they have software. I avoid being clever as much
superior approach for apps at a tactical
a real-time component. The biggest as I can. Because, future me favors
level — an attitude I’ve found just about
easy-to-understand architectures. Simple software breaks less then incrementally make improvements — whether that’s
than fancy software. Simple software can be more easily main- fixing one layout on a page or making one database query run
tained by other people than clever software. Simple software a little bit faster. In a race, speed to market out-paces an ‘ideal
allows engineers to go on vacations. architecture’ every time from my experience.
Pursuing an ideal architecture for me didn’t end with giving So my advice to my younger self If I were to pursue a similar
DDD a go and wrestling through the mess above. If I’m going project all over again is as follows: try the new technologies,
to fail, I’m going to fail hard. That nagging feeling that things design patterns, and other approaches. See what works for
weren’t quite ideal with my designs was very real, and that your project. But above all else: ship the product and ship it
feeling carried out throughout the whole project (and across frequently. Don’t put too much stress into it being as fast as
the team). We migrated the back-end programming language possible or as structurally sound as you feel would be “ideal.”
because of slow sequential API calls we were experiencing The faster you get users to actually use your idea, the more
when querying multiple third-party APIs. Could we have just likely it is to succeed. Most live code has a shelf-life of around
isolated that problem and optimized that one thing? Yes. Yes, 18 months before dependencies are terribly out of date or
we could have. If only you were there to tell me that then ;-). need to be re-structured because of incorrect assumptions. If
I ended up experimenting with Reactive programming you design something that’s still good after 18 months and
(which was drastically faster) but horrible to debug, and we continuing to grow without any changes, great! But maybe,
did similar things with the front end by migrating from JS just maybe, you over-invested in the design and architecture
to TypeScript. We also tried out React-Native for mobile-dev and could’ve been doing something more profitable with your
as well as other ‘hacks’ to ship code faster. These changes did time.
make certain aspects better, but often at the cost of velocity Happy coding!
(learning new things really does take a while; you’re repeat-
edly the beginner). None of these approaches actually solved Jack Peterson is an experienced DevOps
the real problems with the startup. / Site-reliability engineer by day with a
Not all is lost, and it wasn’t all a waste. The good news is that passion for innovation and entrepreneurship.
I did learn a lot of design patterns and gained strong opinions Jack holds a Bachelor’s degree in Finance
about how I should approach subsequent projects. I still appre- and Accounting and decided to turn his
passion into his job — designing, building,
ciate most of the aspects of domain-driven design — even at
and scaling software systems. His first
a tactical level; however, I’ve also loosened my expectations programming language experience was in
on getting an ideal architecture out of the gate with a project. PHP and he continues to support PHP-based
Instead, I aim to get an idea working all the way through and applications in production. @jackdpeterson
1 https://phpa.me/MartinFowler-articles 2 https://phpa.me/document-structure
Summary
We created a transitional architecture, a counterintuitive Listing 3.
way of coding, aimed at facilitating our changing develop-
1. {
ment process. As I’ve presented it here, this architecture is 2. "jsonapi":{ "version": "1.0" },
limited to producing API responses. 3. "meta":{"response":{"code": 200,"message": "Ok"}},
Business logic for each “verb+endpoint” resides in its own 4. "links":{"self": "@php echo Request::url();@endphp"},
Controller class file. Model classes are shared between the 5. @isset($included)
various Controllers and, therefore, contain no business logic 6. "included": @php
whatsoever. The Model classes act as simple API response 7. echo json_encode($included,JSON_PRETTY_PRINT);
formatters. 8. @endphp,
9. @endisset
10. "data": @php
11. echo json_encode($data,JSON_PRETTY_PRINT);
12. @endphp
13. }
3 https://phpa.me/how-static-models
Listing 4. Listing 5.
1. { 1. declare(strict_types=1);
2. "jsonapi": { 2.
3. "version": "1.0" 3. namespace App\Models;
4. }, 4.
5. "meta": { 5. use Illuminate\Database\Eloquent\Collection;
6. "response": { 6. use Illuminate\Database\Eloquent\Model;
7. "code": 200, 7.
8. "message": "Ok" 8. final class LeagueType extends Model
9. } 9. {
10. }, 10. protected function jsonAPI(
11. "links": { 11. LeagueType $LeagueType
12. "self": "http://.../api/infra/v1/league-types" 12. ): array {
13. }, 13. return [
14. "included": [], 14. 'type' => 'LeagueType',
15. "data": [ 15. 'ID' => $LeagueType->id,
16. { 16. 'attributes' => [
17. "type": "LeagueType", 17. 'DisplayName' => $LeagueType->name,
18. "ID": 1, 18. 'Description' => $LeagueType->description,
19. "attributes": { 19. 'IsActive' => $LeagueType->is_active,
20. "DisplayName": "High School Clay Target League", 20. ],
21. "Description": null, 21. ];
22. "IsActive": 1 22. }
23.
23. }
24. protected function jsonAPICollection(
24. }
25. Collection $collection
25. ]
26. ): array {
26. }
27. $jsonAPI = [];
28. /** @var \App\Models\LeagueType $LeagueType */
Ed Barnard had a front-row seat when 29. foreach ($collection as $LeagueType) {
the Morris Worm took down the Internet, 30. $jsonAPI[] = self::jsonAPI($LeagueType);
November 1988. He was teaching CRAY-1 31. }
supercomputer operating system internals 32. return $jsonAPI;
to analysts as they were being directly hit by 33. }
the Worm. It was a busy week! Ed continues 34. }
to indulge his interests in computer security
and teaching software concepts to others.
kindly stop the process and give them an error. In Laravel and need to validate the data differently, then I would structure
API Platform, this is simple; in other frameworks, you need to how I validate slightly differently from how I would validate
find a library that will handle this for you. I am not going to an application that is just a REST API. The rule I tend to
walk through all the available options here because there are a follow is that if I need something more than two times, then
lot of them! However, I do want to give an honorary mention it needs extracting for simplicity of management. Here are
to a platform I met when I visited WeAreDevelopers in Berlin the two examples I will show: a singular REST API and an
last year. They’re called Cerbos, and their approach is slightly application that has a REST API, a CLI interface, a web inter-
different. You run a service in the background, which stores face, and maybe a message-based interface using RabbitMQ
all of the authorization information you might need—you or similar.
then simply ask if this user can do an action or not. They also
have a hosted solution that is pretty cool, and the service on
final class ArticleFormRequest extends FormRequest
your server will use gRPC to make a quick request for you. {
It allows you to go from having a complicated database set public function rules(): array
up on your end to using an IAM approach that is simple to {
use. This is beyond the scope of this article, but I wanted to return [
mention it because, frankly, it’s cool. 'title' => [
'required','string','min:2','max:255'
Alongside these headers that I have already mentioned, ],
there are so many others that I would always use on any 'description' => [
API I am building. From cache headers to security-focused 'required','string','min:2','max:160'
headers—they are the unsung heroes of any API. I won’t go ],
through the basic headers, such as Accept or Content-Type, 'content' => [
'required','string','min:2'
because you should already know what these are. Instead, ],
let’s start with one that isn’t used as often as it should be: ];
X-Content-Type-Option header. It plays an important role in }
your API by helping to protect you against a “MIME sniffing }
attack”, which should always be set to nosniff.
Then, we want to make sure all connections through HTTPS So, what are we doing here? We have a request payload that
are well-enforced. We want to ensure that our API always tells has a title, description, and the content. We want to make
whatever is integrated with it that it will only accept requests sure that we require this data, as we need it to save the article
using HTTPs. This is the Strict-Transport-Security header, properly. Model your validation data based on the minimum
and it will help to protect you against potential downgrade you require to do what you need—while also checking that
attacks and ensure any communication with your API uses anything not required is in the structure or format that it
the encrypted protocol instead of the plain-text protocol. As needs to be. We ensure that these fields are all string based,
you can probably imagine, it’s a good one to set! Especially if which is a built-in Laravel validation approach. Then, I like
you’re letting anyone integrate with your API from anywhere to set a minimum and maximum on any VARCHAR columns I
as long as they have an API Token! need to consider. They typically have a maximum of 255 char-
A less common one that is also useful is Expect-CT, which is acters, so we don’t want to accept a request that will trigger
a handy header that you can set on your API that allows you to a potential database error. Then, I set a minimum that I am
tell browsers and integrations to expect valid certificate trans- willing to accept, which varies depending on the content I am
parency information. This is then registered into the public trying to save. The final parameter here is the content that
CT logs, ensuring that certificates used by your applications does not have a maximum value, as in the database, it is likely
prevent misuse of certificates by Certificate Authorities. to be a TEXT or LONGTEXT. Now that we have a valid payload,
Now, let’s move past headers. They are definitely interesting, we inject this form request into our controller method so
but I am sure you didn’t want to spend all your time reading that Laravel will validate the request before any code in our
about headers! controller method is executed. This is a nice, clean way to
Let’s move on to validation now. Validating payloads that handle it. So, how would this look if I were to be validating
are sent to you is a standard practice. We want to make sure data for multiple interfaces?
what is sent to us is what we expect or want before we even
think about storing it in our database or forwarding it to final class ArticleFormRequest extends FormRequest
another service or API. In Laravel, this is somewhat simple, {
and there are multiple options for how you might handle this. public function rules(): array
I will walk you through how I like to validate request payloads. {
return CreateArticleValidator::rules();
I personally love to use Form Requests in my Laravel appli- }
cation; they’re encapsulated in a simple and clean way. If I }
am building a system with many different interfaces that may
final class CreateArticleValidator doing so enables me to leverage type generics further in the
{ system as required. Let’s take a look: (See Listing 2)
public static function rules(
array $override = []
): array { Listing 2.
return array_merge([
'title' => 1. final readonly class ArticlePayload
['required','string','min:2','max:255'], 2. implements RequestObjectContract
'description' => 3. {
['required','string','min:2','max:160'], 4. public function __construct(
'content' => 5. private string $title,
['required','string','min:2'], 6. private string $description,
], $override) 7. private string $content,
} 8. ) {}
} 9.
10. /**
Using this approach allows you to override the validation 11. * @param array{title:string,
rules where you need to but keeps a consistent standard. Now 12. description:string,content:string} $data
that we have validated our request and are happy with any 13. */
authentication or authorization we need to follow, we want 14. public static function fromRequest(
to take this request data, which is typically an array, and turn 15. array $data
it into something more contextual, as with a stricter type 16. ): RequestObjectContract {
17. return new ArticlePayload(
approach. To do this, we want to leverage DTOs, which is
18. title: $data['title'],
somewhat standard in modern PHP today. This provides the 19. description: $data['description'],
context and strict type setting that we just do not get when 20. content: $data['content'],
we use an array. 21. );
There are many different ways in which you can create the 22. }
DTO from the request, and my advice is to find the one that 23. }
makes the most sense to you. I will walk you through my
approach and explain why I like to do it this way.
(See Listing 1) The common method here would be the fromRequest
method that accepts an array that has a docblock that has the
Listing 1. array shape defined. This helps keep things all lined up, and I
will get as many IDE hints as possible if I miss anything.
1. final class ArticleFormRequest extends FormRequest When processing requests in your PHP application, you
2. { need to have a workflow or method that works for you. It
3. public function rules(): array
should be designed so that you protect yourself against
4. {
rushing later on and against any mistakes you may make by
5. return CreateArticleValidator::rules();
6. } just trying to get stuff done. We have all been there; things
7. always slip past us when the pressure increases and the
8. public function payload(): RequestObjectContract deadlines loom nearer. Designing how you process requests
9. { to protect yourself is the way that you can have a safety net
10. return ArticlePayload::fromRequest( against yourself.
11. data: [ As I always like to look for ways to improve my workflow, I
12. 'title' => $this->string('title')->toString(),
am going to leave you with a thought. Recently, I have been
13. 'description' => $this->string('description')->toString(),
writing a lot of Go. What I love in Go is the struct marshal-
14. 'content' => $this->string('content')->toString(),
15. ], ling and application context that you can always access. If we
16. ); could introduce something like this in PHP, that would really
17. } take what we could do to that next level, making our APIs
18. } stronger and better in the long run.
SubScribE
shop.linuxnewmedia.com
Follow us
@linux_pro Linux Magazine
@linuxpromagazine @linuxmagazine
Thanks to Symfony UX, you can cut down on the amount of There are two types of Twig Components. One is a standard
JavaScript you have to write to do a lot of this. In the previous component that just makes it a bit easier to keep code and its
article, we looked at the basic functionality that Symfony UX associated Twig template together. The second is the same but
introduced through the StimulusBundle, so this month, let’s uses the Symfony UX additions to create something known as
look at some of the additional libraries that you will probably a Live Component. Let’s start with a basic component to see
use when building more interactive user interfaces. how all of this works together. Let’s say we have a blog, and
we want to have a consistent way to output article summaries.
A Quick Reminder of Symfony Ux Let’s turn that into a component.
If you missed the first article, Symfony UX is a suite of bin/console make:twig-component ArticleSummary
libraries that makes it easier to build interactive and dynamic
front-ends in your Symfony application. It does this by
When asked if we want to turn this into a live component,
helping bundle and expose the Stimulus Framework1, which
just say “no”. A component starts with a PHP class that just
is a JavaScript framework that allows us to add metadata to
takes the information you want to pass into the component. It
our application easily to make richer user interfaces.
can also include any business logic to make things easier, like
This is done via the StimulusBundle2, a bundle that sets up
creating a byline. The idea is that we will return articles from
configuration for how Symfony and Twig can interact with
the database, but we can pass them into this component to get
the framework, and a front-end manager3 like Encore or
a summary output.
AssetMapper to build and bundle your JavaScript intelligently.
With a sprinkle of Twig functions, you can add calls to the // src/Twig/Components/ArticleSummary.php
JavaScript controllers you write. namespace App\Twig\Components;
All of this is supported by additional projects, sixteen at the
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
time of this article, allowing you to accomplish many tasks
we use in user interfaces like auto-complete, lazy loading #[AsTwigComponent]
of images, and refreshless interaction. You can even bring final class ArticleSummary
in other frameworks like React or Vue.js to write compo- {
nent-based JavaScript. public string $title;
public string $author;
public string $summary;
Twig Components
public function generateByLine(): string
Most modern JavaScript frameworks have the concept of {
a Component: a small, reusable element that can be used in return 'Written by ' . $this->author;
multiple places but encapsulates all the design and logic in a }
single file. As JavaScript projects become more complicated, }
this design pattern makes finding where the code is easier We will just display the title, a custom byline, and the
and encourages reusability over reimplementation. summary. We use those properties and methods inside the
Symfony UX exposes the idea of a Twig Template, which accompanying Twig template:afef
allows you to create a component in PHP and HTML design
in Twig that can be more easily used across an application. // templates/components/ArticleSummary.html.twig
<div>
composer require symfony/ux-twig-component <h2>{{ title }}</h2>
<h6>{{ this.generateByLine() }}</h6>
{{ summary }}
1 https://stimulus.hotwired.dev </div>
2 https://phpa.me/symfony-bundles
3 https://symfony.com/doc/current/frontend.html
Listing 3. Listing 4.
1. // src/Twig/Components/ArticleSearch.php 1. // src/Repository/ArticleRepository.php
2. namespace App\Twig\Components; 2.
3. 3. namespace App\Repository;
4. use Symfony\UX\LiveComponent\Attribute\AsLiveComponent; 4.
5. use Symfony\UX\LiveComponent\Attribute\LiveProp; 5. class ArticleRepository {
6. use Symfony\UX\LiveComponent\DefaultActionTrait; 6. private array $store = [
7. 7. [
8. #[AsLiveComponent] 8. 'title' => 'Dogs are Fun',
9. final class ArticleSearch 9. 'author' => 'Author 1',
10. { 10. 'summary' => 'Summary 1',
11. use DefaultActionTrait; 11. 'body'=> 'Article about dogs',
12. 12. ],
13. #[LiveProp(writable: true)] 13. // Three other articles
14. public string $query = ''; 14. ];
15. 15.
16. public function __construct( 16. public function getNewestArticles(
17. private ArticleRepository $articleRepository 17. int $limit = 3
18. ){} 18. ): array {
19. 19. return array_slice($this->store, 0, $limit);
20. public function getArticles() 20. }
21. { 21.
22. if (empty($this->query)) { 22. public function searchByTitle(string $query): array
23. return $this->articleRepository 23. {
24. ->getNewestArticles(); 24. $matches = [];
25. } 25. foreach ($this->store as $article) {
26. 26. if (str_contains(
27. return $this->articleRepository 27. strtolower($article['title']),
28. ->searchByTitle($this->query); 28. strtolower($query)
29. } 29. )) {
30. } 30. $matches[] = $article;
31. }
data-model handles wiring our text input to our $this- 32. }
>query property. We can now easily pass information into the 33. return $matches;
component so that our search will work. 34. }
Now, let’s change our homepage to use this new live 35. }
component. We can replace the original for loop with just a
single call to our new component with {{ component('Arti- When we visit the homepage now, we still get the three
cleSearch') }}: articles. If we start to type in the text input, the listings will
automatically update. We did not have to write a single line
// templates/home/index.html.twig of JavaScript!
{% extends 'base.html.twig' %} There is a lot more you can do with components. I would
check out the basic documentation for Twig Components4 as
{% block title %}Hello HomeController!{% endblock %} well as the documentation for Live Components5.
Turbo
{% block body %}
<h1>Sample Blog</h1>
Since we are using the Stimulus package under the hood,
{{ component('ArticleSearch') }} we can also access the other library from Hotwire. This is
Turbo6, which allows you to componentize an entire page
{% endblock %} to get a much more single-page-application experience with
your overall app. One quick win we can get with it is to reduce
We can also stop passing in that original array to the the number of page refreshes as we move around.
homepage template, as that is now handled all by our live
component. Even cooler is that we did not lose any of the
original work we did earlier in the demo because our new
component reuses the old static component. 4 https://phpa.me/symfony-twigcomponent
5 https://phpa.me/symfony-uxlive
6 https://turbo.hotwired.dev
Before we do anything, let’s expand our app a bit so we can We will also add a slug to all of our articles in our Arti-
see Turbo actually working. We currently list out the blog cleRepository:
articles, but now let’s build out a page that actually displays
[
the articles: 'slug' => 'dogs-are-fun',
bin/console make:controller ViewArticle 'title' => 'Dogs are Fun',
'author' => 'Author 1',
'summary' => 'Summary 1',
'body'=> 'Article about dogs',
First, we will modify the route to be dynamic and change ],
it slightly to make a bit more sense. We will use /articles/
{slug}. We will then use the ArticleRepository to find the Now we will make our View Article Twig template display
article with the slug. We will ignore some error checking we the article:
could do to cut down on code. (See Listing 5)
// templates/view_article/index.html.twig
{% extends 'base.html.twig' %}
Listing 5.
1. // src/Controller/ViewArticleController.php {% block title %}
Hello ViewArticleController!
2. class ViewArticleController extends AbstractController
{% endblock %}
3. {
4. #[Route('/articles/{slug}', name: 'view_article')] {% block body %}
5. public function index( <div>
6. ArticleRepository $repo, <h2>{{article.title}}</h2>
7. string $slug <h6>By {{article.author}}</h6>
8. ): Response { <div>{{article.body}}</div>
9. $article = $repo->findBySlug($slug); </div>
10. return $this->render( {% endblock %}
11. 'view_article/index.html.twig', [
12. 'controller_name' =>
13. 'ViewArticleController', Finally, we will update the <body> tag of our base layout to
14. 'article' => $article, add a header at the top so we can more easily get back to the
15. ]); homepage:
16. }
17. } // templates/base.html.twig
<div class="container">
<div class="header">
<h1><a href=" {{ path('app_home') }}">App</a></h1>
We will then modify our ArticleSummary component to </div>
keep track of the slugs to help generate links with Twig’s
path() function: (See Listing 6 and 7) <div class="content">
{% block body %}{% endblock %}
</div>
Listing 6. </div>
1. // src/Twig/Components/ArticleSummary.php
2. #[AsTwigComponent]
3. final class ArticleSummary Listing 7.
4. {
5. public string $title; 1. // templates/components/ArticleSummary.html.twig
6. public string $slug; 2. <div>
7. public string $author; 3. <h2>
8. public string $summary; 4. <a href="{{ path('view_article', {slug: slug}) }}">
9. 5. {{ title }}
10. public function generateByLine(): string 6. </a>
11. { 7. </h2>
12. return 'Written by ' . $this->author; 8. <h6>{{ this.generateByLine() }}</h6>
13. } 9. {{ summary }}
14. } 10. </div>
All Sorts of Other Libraries If nothing else, hopefully, taking a look at both the base
Turbo, Live Components, and Twig Components should Stimulus bundle and code, as well as these libraries, help you
help cover most of the custom user interfaces most people design and create more interactive user interfaces for your
will need to do. There are thirteen other libraries you can applications!
implement to handle auto-complete, lazy loading of images,
and even integrating other frameworks like React and Vue.js. Related Reading
I would highly suggest checking out https://ux.symfony. • Education Station: Symfony UX: Part 1 by Chris Tank-
com7 to see everything that they offer. There are even a ersly, January 2024.
handful of tutorials under the Live Components page for phparch.com/article/2024-01-education-station/
common paradigms where a full library is unnecessary; Live
Components can handle work, like upvote and downvote • Education Station: Generators For Efficient Code by
widgets. Chris Tankersly, December 2023.
www.phparch.com/article/2023-12-education-station/
ct], as
es for php[archite PHP-
many
2nd Edition an existing application and integrate Docker into their work-
giving talks at
flow.
Ohio PHP
d the Northwest
e for Sculpin, a static
ntativ
the PHP
d developer for
• Managing logs
• 12-factor applications
ey
Chris Tankersl
Tankersley
A Handy Class
Valid Hands I’m reusing the Card and Deck classes from last month’s solu-
tion. We’ll need one to represent a set of one or more cards.
First, we need to know the possible valid poker hands. A While this month’s problem asked us to consider 5-card
quick search on Wikipedia1 lists the following eight, assuming hands, I’m not limiting the Hand class shown in Listing 1 to
we don’t allow wild cards: a max number of cards. Typehints and variadics make it a
Hand Example concise class with little boilerplate code.
1 https://phpa.me/wikipedia
2 https://github.com/omerida/PokerHands
Testing for Triples and Pairs the test runner working for us, I can start working on my
solution. With a full house, we need to identify if we have
I started by writing the unit test shown in Listing 2, which a three-of-a-kind and a pair in our hands. We can look
has two tests. In setting up the test, I decided I’d use a class through our hands and calculate the frequency of the values
called HandAnalyzer, which—eventually—can indicate if the of the cards in our hands, regardless of suit. Then, we can
particular hand meets all the requirements for a given poker see if we have at least one triple and one pair. Once we have
hand. To control the inputs, we directly set up the test cases the frequencies, we can use PHP’s array_keys()4 function to
for each hand. In this case, one that is a full house and another retrieve all the values that are a three-of-a-kind or a pair. If we
that is not. If we needed to add other test cases for full house, have one of each, then we have a full house.
we could create them in our test. Since we’re using PHPUnit, Our HandAnalyzer is going to grow pretty quickly. The
I could use its data provider’s3 functionality. methods to identify a full house are shown in Listing 3.
Listing 2. Listing 3.
1. <?php
1. <?php
2.
2.
3. use Puzzles\Cards\{Hearts, Diamonds, Clubs, Spades};
3. namespace Puzzles;
4. use Puzzles\Hand; 4.
5. use Puzzles\HandAnalyzer; 5. class HandAnalyzer
6.
6. {
7. use PHPUnit\Framework\TestCase;
7. private array $freqs = [];
8.
8.
9. class FullHouseTest extends TestCase
9. public function __construct (
10. {
10. private readonly Hand $hand
11. public function testHandIsFlush()
11. ){
12. {
12. $this->analyze();
13. $hand = new \Puzzles\Hand(
13. }
14. new Hearts('K'), 14.
15. new Clubs('K'), 15. private function analyze(): void
16. new Diamonds('K'), 16. {
17. new Clubs(3), 17. $this->countFrequency();
18. new Hearts(3), 18. }
19. ); 19.
20. 20. private function countFrequency(): void
21. $judge = new HandAnalyzer($hand); 21. {
22.
22. foreach ($this->hand->getCards() as $card) {
23. $this->assertTrue($judge->isFullHouse());
23. $this->freqs[$card->value]++;
24. }
24. }
25.
25. }
26. public function testHandIsNotFlush()
26.
27. {
27. public function getTriples(): array
28. $hand = new Hand(
28. {
29. new Hearts('K'),
29. // should a four-of-kind count as a triple?
30. new Clubs('K'),
30. return array_keys($this->freqs, 3, true);
31. new Diamonds('K'),
31. }
32. new Spades(7), 32.
33. new Hearts(3), 33. public function getPairs(): array
34. ); 34. {
35.
35. // again, should a three or four of the same,
36. $judge = new HandAnalyzer($hand);
36. // also be a pair?
37.
37. return array_keys($this->freqs, 2, true);
38. $this->assertFalse($judge->isFullHouse());
38. }
39. }
39.
40. }
40. public function isFullHouse(): bool
41. {
42. $myTriples = $this->getTriples();
The first time I ran the tests, the first one failed. I’d hard- 43. $myPairs = $this->getPairs();
coded my isFullHouse() method to always return true. With 44. return count($myTriples) > 0
45. && count($myPairs) > 0;
3 https://phpa.me/docs-phpunit 46. }
47. }
4 https://php.net/array_keys
From there, we have all the tools to identify hands with just
Listing 4.
a triple, two pairs, and a single pair. I won’t show all three of
1. <?php these solutions; again, see GitHub or the code archive. But the
2. approach is the same from here on out: we set up the hands
3. use Puzzles\Cards\{Hearts, Diamonds, Clubs, Spades}; and expected results in our tests, as in Listing 4.
4. use Puzzles\Hand; Next, we update HandAnalyzer so our tests pass (see Listing
5. use Puzzles\HandAnalyzer; 5)
6.
7. use PHPUnit\Framework\TestCase; We have four of our ten poker hands identified. For the rest,
8. we’ll need tests to test: if the hand has cards of all the same
9. class TripleTest extends TestCase suit, if they’re a sequence, if we have four-of-a-kind, and a
10. { couple of other tests. Our approach is repeatable and sound.
11. public function testHandHasTriple() Once we have our test suite, we can expand it to test dozens or
12. { hundreds of combinations to ensure our solution works in all
13. $hand = new \Puzzles\Hand( cases. We can confidently refactor and optimize our solution
14. new Hearts('K'),
to be more performant without worrying about breaking an
15. new Clubs('K'),
16. new Diamonds('K'), unrelated functionality.
17. new Clubs(3), A good test suite doesn’t slow you down; it accelerates
18. new Spades(5), development and reduces unexpected regressions. The only
19. ); regressions that make it into production are the ones that
20. don’t have tests.
21. $judge = new HandAnalyzer($hand);
22.
23. $this->assertTrue($judge->hasTriple()); Refining the Solution
24. }
25.
I ended up with ten PHP Unit test classes in my final solu-
26. public function testHandHasNoTriple() tion, one for each possible poker hand without whiles. Each
27. { test had two or more cases to verify that I correctly reported
28. $hand = new Hand( the hand but also to check that I didn’t misidentify any. If you
29. new Hearts('K'), look at the solution, you’ll see I left myself comments on how
30. new Clubs('K'), to interpret certain cases. While the number of cases reassures
31. new Diamonds(10), me that the solution is correct, if I had the time, I could add
32. new Spades(7), more variations of hands to run through the HandAnalyzer.
33. new Hearts(3),
I could also look at refactoring the class. First, it assumes
34. );
35. you only ever pass five cards in a hand, but nothing limits the
36. $judge = new HandAnalyzer($hand); hand size. Second, I’d like to use PHP’s new Enums to repre-
37. sent card values to make the code readable and avoid “magic
38. $this->assertFalse($judge->hasTriple()); strings.” These are like performance optimizations to make.
39. } When we analyze a hand, we prepare some internal proper-
40. } ties to help. Perhaps we can avoid some expensive steps, like
sorting the card values, if there is another condition that can
Listing 5. rule out that something is a straight—like having at least one
repeated card rank. With a good test suite, we can make those
1. public function hasTriple(): bool
changes knowing that the test runner will identify regressions.
2. {
3. $myTriples = $this->getTriples();
4. return count($myTriples) > 0;
5. }
6.
Oscar Merida has been working with
7. public function hasExactlyOnePair(): bool PHP since version 4 was released and is
8. { constantly learning new things about it and
9. $myPairs = $this->getPairs(); still remembers installing an early version
10. return count($myPairs) === 1; on Apache. When not coding or writing, he
11. } enjoys RPGs, soccer, and drawing. @omerida
12.
13. public function hasAtLeastTwoPair(): bool
14. {
15. $myPairs = $this->getPairs();
16. return count($myPairs) > 1;
17. }
Listing 1.
1. /**
2. * A class to calculate percentages.
3. * Offers 1 public function that calculates a given
4. * percentage of a value
5. */
6. class PercentageCalculator
7. {
8. /**
9. * A function that calculates the percentage of a
10. * value by taking in a percentage and value
11. * Dividing the percentage by one hundred then
12. * multiplying that result by the value
13. * returning the end result
Another large codebase I worked on had precisely no docu- 34. //returns the float result
mentation at all. This made it really complex to work out what 35. return $percentageDividedByOneHundredMu...;
was going on in some places. Here’s an example of the sort of 36. }
thing that I was dealing with: (See Listing 2 on the next page) 37. }
incorrectly. A bit of searching around eventually finds us the Great, so we now have an explanation of why this is needed,
following class: (See Listing 7) which wasn’t easily visible at the time. It saves us headaches
later, doesn’t it? While it does add a line to our code, it doesn’t
Now it all makes sense! We have another class that’s actually add anything that we already know—it doesn’t remove from
using a cached version of the stock for 60 minutes at a time! the flow of the code and allows us to understand the purpose
Oops! Worse still, we have a lot of unhappy customers. Why for a seemingly odd line of code.
don’t we fix our first class with a “why” documentation: (See
Listing 8)
“What” Documentation
Remember our Collatz conjecture calculation earlier?
Listing 6.
That’s quite a good example of “What” documentation:
1. <?php We’re explaining what’s happening there in quite a compli-
2. cated formula—admittedly, this is on the simpler side of
3. class Product complicated, but it makes the point well. It explains in plain
4. { English what is happening with our functionality here, which
5. public function increaseStock(int $quantity): void isn’t easily explained by just the code.
6. {
Here’s another example: (See Listing 9 on the next page)
7. $this->stock = $this->stock + $quantity;
8. Now, that is a really complicated set of mathematics, right?
9. $this->save(); Some of you may know what this is by its name, but that
10. doesn’t mean everyone does. In fact, the eagle-eyed of you
11. Cache::forget("product_stock_{$this->id}"); will probably notice it’s not a full implementation anyway!
12. } Let’s enhance this with some documentation: (See Listing 10
13. } on the next page)
There we go—much easier to understand—of course, this
Listing 7. is simplified (overall, not just the documentation—the whole
1. class StockLayer functionality isn’t a complete implementation of Mandelbrot).
2. { But now, we can understand this more easily and readily while
3. public function getStock(int $id): int reading our code without going elsewhere to try and work it
4. { out. So, as you can see, I’m explaining WHAT is happening
5. return Cache::remember( when it’s not easily understandable just from the code alone.
6. "product_stock_{$id}", The balance here is difficult to achieve at times—sometimes,
7. now()->addMinutes(60), you might “assume” knowledge when writing something that
8. function () {
turns out not to be true. For example, I’ve been writing PHP
9. return Product::find($id)
code for over 15 years—the first point I learned about the magic
10. ->first()
11. ?->stock ?? 0; __get() method was about four years ago when someone used
12. } it as an example of “common knowledge” while talking to us.
13. ); It was interesting that they assumed everyone knew about it.
14. } Of course, it’s a piece of the underlying language, so I prob-
15. } ably wouldn’t add a comment for it, but it does show the issue
of assuming that people know anything! As a general rule of
Listing 8. thumb, if you find yourself needing to explain what your code
is doing to more than one person, it’s probably time to add
1. <?php
2.
some comments (or refactor the code).
3. class Product
4. { “how” Documentation
5. public function increaseStock(int $quantity): void
6. { The How documentation is a more complicated case—and
7. $this->stock = $this->stock + $quantity; this is all about telling the next person who uses the code
8. HOW to use something. For example, if chainable methods
9. $this->save(); must be used in a specific order for the class to work correctly.
10.
This is a matter of judgment at the time and should be thought
11. // The cache is used in StockLayer, and
out carefully so you’re not telling developers the most simple
12. // forgetting here allows it to recall a new
13. // query in the instance the stock is changed of things. Here’s a contrived example of the sort of thing I’m
14. Cache::forget("product_stock_{$this->id}"); talking about. (See Listing 11 on the next page)
15. } Now, this looks like it should be fine at first glance right?
16. } (I’ve trimmed out a couple of logics in there for brevity). But
Listing 10.
1. class FractalGenerator
2. {
3. public function __construct(
4. private int $width,
5. private int $height,
6. private int $maxIterations
7. ) {}
8.
9. public function generateFractal(): Image
10. {
Listing 9. 11. $image = imagecreate($this->width, $this->height);
12.
1. <?php 13. // loop over each column
2.
14. for ($y = 0; $y < $this->height; $y++) {
3. class FractalGenerator
15. // loop over each row
4. {
16. for ($x = 0; $x < $this->width; $x++) {
5. public function __construct(
17. //set the colour based on the mandelbrot calculation
6. private readonly int $width,
18. $color = $this->calculateMandelbrotColor($x, $y);
7. private readonly int $height, 19.
8. private readonly int $maxIterations 20. //set the pixel at our current location with the
9. ){} 21. //colour in the image
10.
22. imagesetpixel($image, $x, $y, $color);
11. public function generateFractal(): Image
23. }
12. {
24. }
13. $image = imagecreate($this->width, $this->height);
25. // return the image
14.
26. header('Content-Type: image/png');
15. for ($y = 0; $y < $this->height; $y++) {
27. return imagepng($image);
16. for ($x = 0; $x < $this->width; $x++) {
28. }
17. $color = $this
29.
18. ->calculateMandelbrotColor($x, $y);
30. private function calculateMandelbrotColor(
19. imagesetpixel($image, $x, $y, $color);
31. int $x, int $y
20. }
32. ): int {
21. }
33. // Map pixel coordinates to the Mandelbrot
22.
34. // set's complex plane
23. header('Content-Type: image/png');
35. $real = $x / $this->width * 3.5 - 2.5;
24. return imagepng($image);
36. $imaginary = $y / $this->height * 2 - 1;
25. }
37.
26.
38. // Initialize vars for iterative Mandelbrot calc
27. private function calculateMandelbrotColor($x, $y)
39. $zReal = 0;
28. {
40. $zImaginary = 0;
29. $real = $x / $this->width * 3.5 - 2.5;
41. $color = 0;
30. $imaginary = $y / $this->height * 2 - 1;
42.
31.
43. // Iterate until the maximum number of iterations
32. $zReal = 0;
44. // is reached or escape condition is met
33. $zImaginary = 0;
45. while ($color < $this->maxIterations) {
34. $color = 0;
35. 46. // Mandelbrot formula for updating the complex number
36. while ($color < $this->maxIterations) { 47. $zRealTemp=$zReal*$zReal-$zImaginary*$zImaginary+$real;
37. $zRealTemp=$zReal*$zReal-$zImaginary*$zImaginary+$real; 48. $zImaginary = 2 * $zReal * $zImaginary + $imaginary;
38. $zImaginary = 2 * $zReal * $zImaginary + $imaginary; 49. $zReal = $zRealTemp;
50.
39. $zReal = $zRealTemp;
40. 51. // Check if the current complex number has
41. if ($zReal*$zReal+$zImaginary*$zImaginary>=4) { 52. // escaped the Mandelbrot set
42. break; 53. if ($zReal*$zReal+$zImaginary*$zImaginary>=4) {
43. } 54. break; // Escape the loop if condition is met
44. 55. }
45. $color++; 56. $color++;
46. } 57. }
47. 58. return $color;
48. return $color; 59. }
49. } 60. }
50. }
what about the fact that they must be chained in a specific sake of this—imagine that it would throw exceptions along
order? the path, as I did this again for brevity)
In this case, our use case is:
1. createOrder(‘abc123’)
2. addItem(‘ab’, 2) Listing 12.
3. applyDiscount(12.5)
1. <?php
4. processOrder() 2.
3. /**
If you don’t create the order before adding an item, it will 4. * Order Processor requires a specific usage order:
fail. If you don’t add an item(s) before applying the discount, 5. * 1) Create the Order
it will fail. If processing an order isn’t last, it will fail. In this 6. * 2) Optionally add items (any number of times)
case, the process breaks (admittedly, php would help with the 7. * 3) Optionally add a discount (only once)
array, but that’s an implementation detail generated for the 8. * 4) Process Order (always last)
9. *
10. * example:
Listing 11. 11. * $orderProcessor = new OrderProcessor()
12. *
1. <?php 13. * $orderArray = $orderProcessor
2. 14. * ->createOrder('abc123')
3. class OrderProcessor 15. * ->addItem('ab', 2)
4. { 16. * ->addItem('cd', 4)
5. private ?array $order = null; 17. * ->applyDiscount(12.5)
6. 18. * ->processOrder();
7. public function createOrder(string $orderId): self 19. *
8. { 20. * The minimal usage (for an empty order) would be:
9. $this->order = [ 21. *
10. 'id' => $orderId, 'items' => [] 22. * $orderProcessor = new OrderProcessor()
11. ]; 23. *
12.
24. * $orderArray = $orderProcessor
13. return $this;
25. * ->createOrder('abc123')
14. }
26. * ->processOrder();
15.
27. */
16. public function addItem(
28. class OrderProcessor
17. string $item, int $quantity
29. {
18. ): self {
30. private ?array $order = null;
19. $this->order['items'][] = [
31.
20. 'item' => $item, 'quantity' => $quantity
32. public function createOrder(string $orderId): self
21. ]; 33. {
22. 34. ...
23. return $this; 35. }
24. } 36.
25. 37. public function addItem(
26. public function applyDiscount( 38. string $item, int $quantity
27. float $discountPercentage 39. ): self {
28. ): self { 40. ...
29. // Apply discount logic here 41. }
30. $this->order['discount'] = $discountPercentage; 42.
31. 43. public function applyDiscount(
32. return $this; 44. float $discountPercentage
33. } 45. ): self {
34. 46. ...
35. public function processOrder(): array 47. }
36. { 48.
37. // Process order logic here 49. public function processOrder(): array
38. $finalOrder = $this->order; 50. {
39. $this->order = null; 51. ...
40. 52. }
41. return $finalOrder; 53. }
42. }
43. }
Here’s the documentation I would include for this class: (See Listing 12)
This explanation will be invaluable to a user and would also, in most editors, be visible when instantiating the class. It’s also
worth bearing in mind that sometimes, if you’re using a framework, some things might need to happen in an odd order to
handle the underlying framework.
Achieving optimal code documentation is a delicate balancing act that requires developers to steer clear of both over-docu-
menting and under-documenting. Striking the right balance involves identifying the essential types of documentation: Setup
Documentation, Documentation By Inference, “Why” Documentation, “What” Documentation, and “How” Documentation.
The provided examples illustrate the importance of contextual and concise documentation to enhance code clarity and main-
tainability. By adopting a thoughtful approach, developers can create documentation that adds value without overwhelming
the codebase or leaving it devoid of critical insights. Looking ahead, the next article in this series will tackle a provocative
question: “Is Your Code Clear Enough That A Non-Developer Can Understand?” This discussion will delve into the art of
crafting code that tells a compelling story, making it accessible not only to seasoned developers but also to individuals with
limited technical backgrounds. By incorporating the principles and insights explored in this series, developers can aspire to
create codebases that transcend technical jargon, fostering a more inclusive understanding of their work.
In 1983, Christopher was introduced to computers by his dad, at the tender age of 3. now, over 40 years later, he has
been working in the industry for over 20 years making an impact across multiple sectors of the industry. Starting with
launching the first web development company in Staffordshire, Christopher dealt with the web - when the web was
little more than just pretty text. He established the websites for many different businesses in their first inception, before
moving onto web applications a little while later. Illness prevented Christopher from working in the industry full time
for some considerable time - but recovery meant he could tackle once again the joys of code - but he soon found that
his skills had become out of date, so thanks to the School of Code in the UK he was able to return to the workplace with
revitalised skills ready to tackle the next wave. Specialising since the School of Code in Readable Code, He has worked
with a large number of languages, specialising in supporting businesses to grow standards for their code base, and now
he is ready to share his processes with the world. @ccmiller2018
Cheating is Encouraged
Eric Mann
“Never memorize something you can look up.”—Albert Einstein.
I hated taking tests in school. The focus on rote memoriza- general themes of the industry and leverage deep dives into
tion and timed regurgitation of facts did little to evaluate the specific topics where appropriate to build further strength.
mastery of any particular topic properly. In fact, I’ve prob- Anyone reading this column most likely writes PHP, so one
ably forgotten orders of magnitude more information than of the first places they should look would be OWASP’s cheat
I retained during all those study sessions long ago. Seeing sheet on PHP itself.
a similar process in coding exercises during job interviews
frustrates me. Why memorize something that could be easily
referenced in documentation somewhere else?
Deep Dive: PHP Configuration
The justification is less for full memorization and more While all of OWASP’s cheat sheets are relevant for modern
for a sense of where to start with looking up reference mate- developers, there are quite a few, and it’s often difficult to
rial. You know you want your application to be secure, and know where to start. One of the more relevant sheets for PHP
you haven’t memorized the full breadth of material relating artisans would be their PHP Configuration cheat sheet3.
to cybersecurity—where do you start? Especially in an age This sheet is inspired by earlier work
where technology evolves at a rapid pace, having at least some by ParagonIE4 and Guardrails.io5. My
foundation with the material makes it infinitely easier to look bookSecurity Principles for PHP Applicationsalso
something up when needed. covers much of the material. The entirety of the cheat sheet
The Open Web Application Security Project (OWASP) addresses default configuration settings you should be using
documents many different application security risks, focusing within php.ini to prevent some of the most critical issues
primarily on those most commonly seen in the wild. They your application could face.
produce training around application security and publish For example, whiletrackingerrors in production is vital to
both security overviews and walkthroughs on a regular identifying and debugging any break-fix issues, exposing
basis. Most helpful, they publish a series of cheat sheets1 to those errors to visitors is a poor practice. In fact, doing so
help with security configuration and best practices without might expose additional information to a potential attacker
requiring you to memorize everything in order to get started. they could use to further compromise or negatively impact
your application. Instead, it’s best practice to set display_
errors to Off so that any breaking issues are never exposed
OWASP Cheat Sheets on the front end.
OWASP’s cheat sheets cover everything from AJAX secu- Another configuration setting recommended by the cheat
rity to “forgot password” systems, threat modeling, and sheet and any competent security overview for PHP is the
XML external entity attacks. They enumerate the baseline decisive use of disable_functions to prevent the use of
configuration for many popular frameworks and standard dangerous function calls like eval() or system().
applications, making it easier for you and your team to focus ⚠️ There might be legitimate reasons for your application
on your application rather than the systems it relies on. Even to leverage functions like popen() or passthru() or putenv(),
then, the concepts and topics covered in the cheat sheet series all of which are discouraged by the OWASP cheat sheet. If
can help render your application more secure by allowing you need access to these functions, leave them enabled. But
you to review specific functionality or even focus on secure be sure your team audits exactly what is happening and why
product design2 from first principles. and can make an informed decision as to which functions
In modern application development, trying to memorize are allowed, as every function enumerated in OWASP’s walk-
and know every possible topic within a cybersecurity domain through could be used by an attacker to cause irreparable
is a fool’s errand. No one in the industry has such encyclopedic harm to your application or the system hosting it.
knowledge—even if someone were to cultivate such a deep In addition, you should take care to properly tune session
understanding today, the vulnerabilities of tomorrow would management and file handling within your application. This
rapidly erode the value and applicability of that knowledge. is due to the way both systems can be leveraged by attackers
Instead, it’s valuable to have a broad understanding of the to trigger denial of service attacks against your application
3 https://phpa.me/cheatsheetsPHP
1 https://cheatsheetseries.owasp.org/index.html 4 https://phpa.me/paragonie
2 https://phpa.me/cheatsheetseries 5 https://phpa.me/github
by exhausting available system resources. Choosing minimal new techniques or compromises are discovered. For example,
defaults protects your system and your users and helps build 2014’s Shellshock vulnerability in Bash9 existed in the soft-
a solid reputation for your team as shepherding secure appli- ware undetected for 25 years!
cations. The ongoing security of your product relies on a practice of
There are plenty of other cheat sheets available that will continuous improvement. Your team should regularly review
help with your application. Those building atop Laravel will its entire stack, every application endpoint, and any source
want to review its framework-specific cheat sheet6—the same of technical debt. Baseline configurations for third-party
goes for those leveraging the Symfony framework7. Likewise, packages can and will evolve as well, so following a “set and
anyone interfacing with MySQL or any other database will forget” approach with those systems is a recipe for disaster.
want to ensure it’s configured securely8. Take time to review what you’ve built, what you’ve deprecated,
what you’ve refactored, and even work your team has inten-
Continuous Improvement tionally deprioritized to ensure your customers are safe and
your product is secure.
OWASP itself isn’t a finished product. The organization’s And always keep an eye on OWASP’s cheat sheets for new
list of the top ten most experienced vulnerabilities updates learning and approaches to help strengthen your team’s efforts.
regularly—usually every three years—based on feedback
from practitioners in the industry. With every updated list
of application security risks comes new training and updated
cheat sheets. Eric is a seasoned web developer experi-
Likewise, no application is ever fully complete. Mature? enced with multiple languages and platforms.
Yes—you can and absolutely will reach a point where ground- He’s been working with PHP for more than
a decade and focuses his time on helping
breaking innovation begins to slow down as your product
developers get started and learn new skills
stabilizes. But even the most secure product today can be with their tech of choice. You can reach out
proven insecure in the future as our knowledge improves and to him directly via Twitter: @EricMann
6 https://phpa.me/cheetsheatLaravel
7 https://phpa.me/cheatsheetSymfony
8 https://phpa.me/cheatsheetDatabase 9 https://phpa.me/wikipedia
Get it
FASt
with a digital
subscription!
Here we are again looking at another package presented the registry), in case the order of the listeners is important.
by The PHP League1, simply called Event2. This library builds PSR-14 does not put restrictions on this as it is completely
upon interfaces recommended in PSR-14: Event Dispatcher3. dependent on the application. Consider that it is crucial that
PSR-14 presents a set of interfaces that can be used commonly some listeners MUST execute before others:
in libraries and frameworks to support collaboration and
$dispatcher->subscribeTo(
reuse. An Event Dispatcher is a well-used and battle-tested $eventIdentifier,
design principle where developers can easily inject func- $listenerMostImportant,
tionality into an application, especially legacy applications. 1
By dispatching events in your application, you can dramat- );
ically reduce the complexity of refactoring your code. New $dispatcher->subscribeTo(
$eventIdentifier,
code can be added almost seamlessly to legacy code, bridging $listenerSomewhatImportant,
that gap to the new code. In this article, we’ll look at how the 10
Event package is implementing PSR-14 and building upon it, );
presenting us with a simple to implement library for handling $dispatcher->subscribeTo(
events in your application. $eventIdentifier,
$listenerNotImportant,
Installing the Event package is straightforward and is done 100
using composer to pull it by the name league/event or from the );
repository at github4 (the latest version is 3.0.2). In the exam- $dispatcher->dispatch($event);
ples of code in this article, we’ll assume we’ve used composer
and have our vendor folder with an autoloader. The Event Here, we have three listeners in order of importance. On
package provides a dispatcher and supporting utilities; it does dispatch, the listeners are retrieved, sorted by their priority
not provide events or listeners. Those are objects best created value, and executed, starting with the first one, $listener-
by the developer with the ecosystem in mind. The dispatcher MostImportant.
is told which listeners respond to the events. It receives an Before we go further on listeners, let’s discuss the event, the
event, locates the listeners subscribed to it, and then calls object starting the entire process. The Event package expects
those objects, passing the event to them. an event to be an object. It can be a class or stdObject : (See
Setting up the dispatcher is very simple: Listing 1)
By using ListenerProvider, we can pass our array of listeners standards, it is compatible with frameworks and libraries
where it can iterate and subscribe the listeners. When the expecting the same. It offers additional useful features, too,
event.simple event is dispatched, SimpleListener and LogLis- like the EventGenerator, which can prepare multiple events
tener are invoked. The event.issue event will be dispatched and collect them to be dispatched at one time. The Buffere-
to LogListeneralone. dEventDispatcher is an excellent utility to use in something
The PHP League’s “Event” package is an excellent, clean like a functional process, where events could be triggered
example of PSR-14 in the real world. By following PSR-14 but do not need to be dispatched to listeners immediately. As
provided in the documentation: (See Listing 5)
Listing 5. In that example, events are queued up until finally
dispatched when dispatchBufferedEvents() is called.
1. use League\Event\BufferedEventDispatcher; I hope the reader found this example of PSR-14 in action
2. use League\Event\EventDispatcher; useful and relatable, and how helpful using events in an appli-
3.
cation can be. Be sure to check out the documentation for
4. $internalDispatcher = new EventDispatcher();
5. $internalDispatcher->subscribeTo( Event5 at The PHP League and take some time to look at the
6. stdClass::class, other packages there, too.
7. fn () => echo "Hello!"
8. ); Frank Wallen is a PHP developer, musician,
9. and tabletop gaming geek (really a gamer
10. $dispatcher = new BufferedEventDispatcher( geek in general). He is also the proud father
11. $internalDispatcher of two amazing young men and grandfather
12. ); to two beautiful children who light up his
13. life. He is from Southern and Baja California
14. // no listener is called; and dreams of having his own Rancherito
15. $dispatcher->dispatch(new stdClass()); where he can live with his family, have a dog,
16. // no listener is called; and, of course, a cat. @frank_wallen
17. $dispatcher->dispatch(new stdClass());
18.
19. // listener is called twice
20. $dispatcher->dispatchBufferedEvents();
5 https://event.thephpleague.com/3.0/
Do we even need these things? high-performance, scalable, concurrent TCP, UDP, Unix
If you’re a newer developer, you may question why we Socket, HTTP, and WebSocket services with PHP syntax.
need tools like Swoole or PHP-FPM. Whether using Nginx An Overview of Roadrunner
or Apache to serve your application to the internet, these Roadrunner, like Swoole, is an open-source, high-perfor-
web servers have several ways of running PHP. You can use mance PHP application server, load balancer, and process
a CGI (Common Gateway Interface), run inside the server manager. It works by maintaining a preloaded application
process, or do a proxy amidst other methods. Within Apache, state in memory, tremendously reducing the overhead of the
some systems will use mod_php, which runs the PHP inside PHP bootstrapping process. Roadrunner is written in Go and
the Apache process. If you are accustomed to using Nginx, designed for PHP applications of all sizes that require long-
then you are likely using PHP-FPM, which processes the living processes.
PHP scripts separately and relays the response to Nginx via Speed and Performance
the CGI. Generally speaking, most server setups these days Both Swoole and Roadrunner are known for their speed
utilize Nginx, which is more configurable. PHP-FPM (PHP and performance. Swoole shines with its C-powered perfor-
FastCGI Process Manager) is designed to handle a higher mance and coroutine-based model, which handles multiple
load of websites, deliver peak website performance, and requests concurrently. On the other hand, Roadrunner, due
increase web application speed through process and connec- to keeping the application state in memory, significantly
tion management. It also helps improve PHP performance by improves the PHP application’s performance by reducing the
not recompiling PHP code for every request. bootstrapping overhead.
How are Swoole and Roadrunner different from PHP-FPM? Concurrency
Unlike PHP-FPM, which is triggered as a CGI process to run Swoole provides native support for coroutines, eliminating
when Nginx or another web server handles a request, Swoole the need for callbacks and resulting in cleaner and easier-to-
and Roadrunner have their own “server” running locally on read code. However, while Roadrunner doesn’t inherently
your machine. In this case, Nginx can proxy connect to the support PHP coroutines, it provides a stable environment
“server” of Swoole or Roadrunner and relay back the response. for long-running PHP applications and maintains efficient
This proxy pattern enables Roadrunner and Swoole to have memory usage even under heavy loadings.
long-running or primary processes that accept requests and Platform Compatibility
hand them off to tasks. The benefit here is that Swoole and Both platforms are decently compatible with existing PHP
Roadrunner maintain the application in memory. In turn, frameworks. Swoole, however, offers extensive compatibility
because the system is in memory, these systems can handle options and can be integrated with popular PHP frameworks
more requests concurrently, increasing performance overall. like Laravel and Symfony. Conversely, Roadrunner is mostly
NodeJS handles requests in a very similar manner with a bundled with Spiral Framework but also offers compatibility
long-running process. with Symfony, Laravel, and Zend.
An Overview of Swoole Ease of Use
Swoole is an open-source, high-performance PHP corou- With clear syntax and simplicity of use, Swoole emerges
tine server and networking communication engine. It blends as the more appealing option due to its fundamentally PHP
the speed of C-language and the simplicity of syntax from syntax-based applications. Roadrunner, meanwhile, requires
PHP, facilitating rapid development of concurrent applica- some knowledge of Golang, which could be a learning curve
tions. Swoole’s coroutine model supports synchronous and for PHP developers.
asynchronous functions, enabling PHP developers to build One more thing
With the advent of these engines replacing tools like performance and efficient memory usage, something, in my
PHP-FPM and other components, we end up with tools experience, most PHP developers struggle with.
like Laravel Octane and others, which help bridge the gap, Lastly, before we conclude this high-level analysis, let’s see
enabling Laravel developers to harness faster engines like some benchmarks. I located a concise blog post covering this
Roadrunner or Swoole. However, although tools like Octane on https://scratcher.dev1.
can help bridge the gap, some frameworks like Roadrunner
Swoole handled 8273 requests in 30s, or 275 rps
add significant knowledge overhead to an application, which Roadrunner handled 11916 requests in 30s, or 396 rps
may be better off with a simple load balancer and multiple
server deployments. They also noted that in their case Swoole was not utilizing
I would also be amiss if I didn’t mention FrankenPHP any of its capabilities with Concurrent Tasks, Ticks & Inter-
and another framework I came across recently: Resonance. vals.
Resonance is based on Swoole, and FrankenPHP is written in https://phpa.me/benchmark-rr-vs-swoole2
Golang. Resonance has built-in Attribute processors, its own Regardless of the choice, both Swoole and Roadrunner
Controller system, and baked in GraphQL. FrankenPHP, on are excellent options for high-performance PHP develop-
the flip side, requires Caddy, which is less popular than Nginx, ment, ensuring the delivery of robust, scalable, and efficient
though it is growing. It also has fewer features than Road- web-based applications. Overall, it’s a win-win situation for
runner but enables you to deploy into a Kubernetes cluster. PHP developers who want to breathe life into their web appli-
The recommendation here boils down to this: if you’re cations and harness infrastructure solutions that are more
looking to truly harness something like Swoole, Road- modern and enterprise-oriented.
runner or another PHP server tool, you need to consider
your primary goal. If you want to deliver a monolith on Matt has been developing software for over
more robust infrastructure such as Kubernetes or Serverless 13 years. He started his career as a PHP
systems, harnessing Roadrunner or FrankenPHP could be a developer for a small marketing firm but
fast win. If you’re looking at diving into Swoole or Resonance, has since worked with a few Fortune 500
you’re better off building an application or a micro-service companies, led a couple teams of developers,
from scratch. It is often more advantageous to make code that and is currently a Cloud Architect for a
significant Travel technology company. He’s
is focused on a specific framework’s “way” of doing things
contributed to the open-source community
rather than asking an application to adopt an ad hoc solution on projects such as Cordova and Laravel.
that requires understanding multiple frameworks for a single He also made numerous packages and has
monolith. helped maintain a few. He’s worked with
Ultimately, the choice between Swoole and Roadrunner start-ups and sub-teams of big teams within
will depend on the PHP developer’s specific needs and large divisions of companies. He spends
expertise and their desire to focus on DevOps. Both have time with his wife and kids when he’s not
their strengths. Swoole offers more straightforward syntax, tinkering with code or learning new technol-
extensive compatibility, and advanced concurrency support. ogies. @Mattylantz
In contrast, Roadrunner shines with its improved application
1 https://scratcher.dev
2 https://phpa.me/benchmark-rr-vs-swoole
gs
You g Thin
w
Kne
ettin
g
For
What was I thinking?
Did I Do This?