0% found this document useful (0 votes)
14 views

phparchitect-2024-02

The February 2024 issue of php[architect] focuses on Domain-Driven Design (DDD) and its importance in software development, emphasizing collaboration and understanding complex business needs. It features articles on various topics, including API essentials, Symfony UX, and code documentation, along with insights on testing tools and security practices. The issue aims to equip PHP developers with knowledge and strategies to navigate the evolving landscape of software development.

Uploaded by

marek.rode
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
14 views

phparchitect-2024-02

The February 2024 issue of php[architect] focuses on Domain-Driven Design (DDD) and its importance in software development, emphasizing collaboration and understanding complex business needs. It features articles on various topics, including API essentials, Symfony UX, and code documentation, along with insights on testing tools and security practices. The issue aims to equip PHP developers with knowledge and strategies to navigate the evolving landscape of software development.

Uploaded by

marek.rode
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 48

FEBRUARY 2024

www.phparch.com Volume 23 - Issue 2

The Magazine for PHP Developers

THE PHP GAMBIT:


WINNING STRATEGIES
IN CODE
An Overview of Domain-Driven Design
Domain Driven Design is Great; But Don't Stress About It

Also Inside:
   ­   ­€‚ ­ ƒ  Ž „ „ƒŽ 
    „  „  Ž‘Š € Œ
„€ ' „
     †€‡ Ž ‹ Ž„ ’ ‹
    €  ˆ‰‰ 
Š‹Œ
a php[architect] print edition

Learn how a Grumpy Programmer approaches


improving his own codebase, including all of the
tools used and why.
The Complementary PHP Testing Tools Cookbook is
Chris Hartjes’ way to try and provide additional tools
to PHP programmers who already have experience
writing tests but want to improve. He believes that
by learning the skills (both technical and core)
surrounding testing you will be able to write tests
using almost any testing framework and almost any
PHP application.
Available in Print+Digital and Digital Editions.

Order Your Copy


https://phpa.me/grumpy-cookbook
FEBRUARY 2024
Volume 23 - Issue 2

3 An Overview of Domain-Driven 30 Is Your Code Documented Enough


Design To Help?
Rob Allen Readable Code
Christopher Miller
9 Domain Driven Design is Great; But
Don’t Stress About It. 37 Cheating is Encouraged
Jack Peterson Security Corner
Eric Mann
13 Transitional Implementation: Part 1
2500 Feet 39 Using Different Browsers
Edward Barnard Barrier-Free Bytes
Maxwell Ivey
17 API Essentials: A Developer’s Guide
to Authentication, Validation, and 41 PSRs In Action: PHP League Event
DTOs Package
API Guy PSR Pickup
Steve McDougall Frank Wallen

44 A Comparative Analysis of Swoole


21 Symfony UX: Part 2 vs Roadrunner
Education Station RADAR
Chris Tankersly Matt Lantz

27 Five Card Stud 46 Dear Past Me, What Were You


PHP Puzzles Thinking?
Oscar Merida finally{}
Beth Tucker Long

Edited by a Kenny Rogers Wannabe Advertising Copyright © 2024—PHP Architect, LLC


php[architect] is published twelve times a year by: To learn about advertising and receive the full All Rights Reserved
prospectus, contact us at [email protected]
Although all possible care has been placed in
today!
PHP Architect, LLC assuring the accuracy of the contents of this
9245 Twin Trails Dr #720503 magazine, including all associated source code,
Contact Information: listings and figures, the publisher assumes no
San Diego, CA 92129, USA General mailbox: [email protected] responsibilities with regards of use of the information
Subscriptions Editorial: [email protected] contained herein or in all associated material.
Print, digital, and corporate Print ISSN 1709-7169
subscriptions are available. Visit php[architect], php[a], the php[architect] logo, PHP
Digital ISSN 2375-3544 Architect, LLC and the PHP Architect, LLC logo are
https://www.phparch.com/magazine to subscribe
or email [email protected] for more trademarks of PHP Architect, LLC.
information.
Editorial

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

An Overview of Domain-Driven Design


Rob Allen
Domain-Driven Design (DDD) is an approach to software development that focuses on
understanding and dealing with complex business needs. We will explore the strengths of
DDD, including its approach to structuring software around an abstract model of a business
problem and creating a ‘ubiquitous language’ for communication among developers and
domain experts. We’ll look at the strategic and tactical aspects of DDD and show that the
central benefit of this approach is improved collaboration, better understanding of business
processes, and ultimately, better and more flexible software.

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

www.phparch.com \ February 2024 \ 3


An Overview of Domain-Driven Design

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

4 \ February 2024 \ www.phparch.com


An Overview of Domain-Driven Design

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

www.phparch.com \ February 2024 \ 5


An Overview of Domain-Driven Design

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

6 \ February 2024 \ www.phparch.com


php[architect]
[consulting]
Get
customized
solutions for
your business
needs
Leverage the
expertise of
experienced
PHP Create a
developers dedicated team
or augment
your existing
team

Improve the Building


performance cutting-edge
and scalability solutions using
of your web today's
applications development
patterns
and best
practices
[email protected]
FEATURE

Domain Driven Design is Great; But Don’t


Stress About It.
Jack Peterson
Let’s get things out of the way: I’m no longer a fan of ivory tower ideals for small projects.
Chalk it up to my own ignorance or perhaps being a self-taught developer. Or, maybe it was
my friend who worked at a FAANG who would proudly speak about all of the ‘right ways’ to
do code (he was an ‘L4’ engineer, after all) – he’d poke fun at my ‘pitiful PHP language’ on
a startup project I was working on. He’d ask me why I was trying to solve problems ‘already
solved by other languages and better frameworks.’ I’d go home and reflect on his critiques.
Frustratingly, many were valid. I’d second-guess my decisions. After a while, I also bought into
the idea of learning (and mastering) the concepts of DDD while building out my startup app
idea with a small team of developers. Mistakes were made.
We were going to build a wishlist for the real world. The My role in the startup: Technical Founder (Visionary + API
idea wasn’t particularly novel, but it did solve two problems Engineering + DevOps). One good friend: front-end JS guru.
for me: First, when I’d go out shopping with my wife, she’d And one more friend (who also happened to be a former
find items and say something along the lines of, “That’s a great colleague) would handle the iOS side of things. We also had
idea for your nephew’s birthday” and then move on. She has a few additional folks whose role was contingent on the app
a natural talent for gift-giving. I’d try to capture that thing gaining traction. We decided to hire an Android developer as
because I, on the other hand, do not have that personality we approached the finish line. It was beautiful; everyone had
trait. Second, I’m price-conscious and always have been. I a clear role, and we were going to build a product the ‘right’
want to know if I’m getting a good deal and prefer to save way. We were each pretty good at what we did and had all
money wherever possible. launched multiple services into production for real compa-
Sounds like a new app idea to me! So, I embarked on nies with real customers, but we also all had a shared chip on
building an app that starts with the foundational element, the our shoulders.
GTIN/UPC/ISBN/EAN (barcode). Users could create these We all worked for companies that had consistently priori-
“snaps” take pictures, categorize them into a few buckets, and tized business-oriented decisions over engineering-oriented
then hit up APIs for price comparison. The flow was pretty decisions. We were all tired of supporting code that was trash
simple: in our not-so-humble opinions. And as “smart” engineers, we
• Scan the product. were going to build beautiful, maintainable, and understand-
• Fetch information about the product from the barcode able code. Oh, we’d have unit tests, we’d adopt DDD with rich
and ask the user to snap pictures if we didn’t already domain models, and we’d build a well-designed API. We’d split
have imagery. architecture between front-end and back-end, and each of us
• Allow them to categorize the Snap to remember it for would pick the best tools/frameworks available at the time
later. for the given task at hand. Micro-services? You bet we did
We’d scrape for prices and then display a comparison for that too (with an Authorization Server that really ended up
online and local retailers. We did the proper startup flow. We soaking up a lot of development time). You know, the big-boy
talked to many people to see what they thought about the smarty-pants engineering decisions – we’d do them all.
idea. Everyone “loved the idea”. “I’ve thought about that too,” The startup failed. Not for lack of effort, mind you. Each
or “That’s a great App idea,” they’d say. So we built it. Every time we changed something, it was for good technical or user
version added requested features and fixed bugs, but the app’s feedback-provided reasons. No, we worked on this idea for
usage didn’t grow. Real-world testing proved an awful expe- over seven years with many cycles of feedback gathering from
rience. Products would be missing (no, Amazon doesn’t sell end-users, followed by feature changes, bug fixes, and subse-
everything). Product images would be wrong (yeah, UPC quent releases. We successfully shipped three major versions
databases have low-quality data). Scan a Lego box and get across all mobile platforms (each release had a fundamentally
back a picture of a box of Cheerios. And, no matter what we different UX), as well as countless iterations on the web app.
changed, the UX never really felt quite ‘right’. We (slowly) Each release would get positive feedback from family and
learned that Ideas ≠ reality. friends (they love you, and as such, they will tell you what
they think you want to hear, not what you need to hear). We

www.phparch.com \ February 2024 \ 9


Domain Driven Design is Great; But Don’t Stress About It.

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

10 \ February 2024 \ www.phparch.com


Domain Driven Design is Great; But Don’t Stress About It.

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

www.phparch.com \ February 2024 \ 11


Domain Driven Design is Great; But Don’t Stress About It.

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

You’re the Team Lead—Now What?


Whether you’re a seasoned lead developer or have just
been “promoted” to the role, this collection can help
you nurture an expert programming team within
your organization.

After reading this book, you’ll understand what pro-


cesses work for managing the tasks needed to turn a
new feature or bug into deployable code.

Order Your Copy


https://phpa.me/devlead-book

12 \ February 2024 \ www.phparch.com


2500 Feet

Transitional Implementation: Part 1


Edward Barnard
Our transitional PHP code looks odd. It’s counterintuitive. But it makes highly efficient use of
developer effort. This code walkthrough explains the reasons behind our methods. Part 1 presents
the basic architecture. Part 2 explains why we’re doing it this way.

You generally can’t fix a broken development process by Figure 1.


writing code. However, once the process has begun to change
and evolve, it’s quite possible to write code supporting that
process.
In support of your changing process, that new code may
seem counterintuitive. It does not express the end goal. It’s a
necessary step toward eventually reaching that end goal.
This transitional architecture1 may well be in place for
several years. Thus, it needs to be solid and reliable, even
though it might not be structured the way you’d expect.
As we walk through my current project code, remember
that the code itself does not matter. It’s supporting a specific
process undergoing change. Other transitional processes and
their requirements will undoubtedly be different.
What’s important here is the reasoning and thought process.
The key skill is thinking about your process and identifying
ways to write software supporting your process that’s in tran-
sition.
“Code supporting a development process” is an odd concept.
That’s why it’s important to do more than write the code.
Leave the next developer (which may well be Future You)
clues explaining what you’ve done and why.
What do I mean by oddly-structured code? For example,
I’ll show you PHP code that should be in the “Model” portion
of an MVC (Model-View-Controller) PHP application. I
placed that code in the Controller rather than in the Model.
This placement is counterintuitive, so I’ll explain why we’re Controller Method
taking this approach.
Listing 2 shows the Laravel Controller in question. There’s
not much here to see. That’s on purpose so we can focus on
Laravel Route what is there.
I’m building a Laravel 10.x application consisting of API We format our responses according to the JSON:API2 docu-
endpoints. We don’t care about the application’s purpose or ment structure. Generally speaking, we return the desired
such details as authentication and authorization. We only result set at the top-level data key and related information in
care about understanding this application’s Transitional the top-level included key.
Architecture aimed at supporting our development process. Our use of included is quite flexible, meaning we toss
Listing 1 shows our starting point, a Laravel route defini- in whatever we feel like placing there! For example, if the
tion in routes/api.php. This endpoint returns a list of league request included join conditions that are not part of the result
types. Remember, we don’t care what a “league type” is. set, that information can reside in the included section. We’ll
We’re focused on code structure. Figure 1 shows the request/ see examples of this below. In this trivial example, included
response flow covering Listing 1 through Listing 5. is empty.
When reaching this endpoint with an http GET, Laravel The database access is Listing 2, line 21. We are loading all
transfers control to method InfraLeagueTypesControl- active LeagueType data rows (is_active === 1), in ascending
ler::index(). order according to the primary key id. Laravel returns the

1 https://phpa.me/MartinFowler-articles 2 https://phpa.me/document-structure

www.phparch.com \ February 2024 \ 13


2500 Feet
Transitional Implementation: Part 1

result set as a Collection object, which in this case becomes


Listing 1.
input to LeagueType::jsonAPICollection() at line 20.
The result of LeagueType::jsonAPICollection(), an array, 1. <?php
becomes the array $data. Line 24 renders the JSON:API 2.
response, incorporating $included and $data. 3. use App\Http\Controllers\InfraLeagueTypesController;
That’s the structure and flow. Let’s look at the view template 4. use Illuminate\Support\Facades\Route;
5.
and its output.
6. Route::get(
7. 'infra/v1/league-types',
View Template 8. [InfraLeagueTypesController::class, 'index']
9. );
Listing 3 shows our Laravel Blade template, resources/
views/api/200.blade.php.
Compare this template to a sample response body, Listing Listing 2.
4. I stripped the data array down to a single result for brevity.
1. <?php declare(strict_types=1);

Formatting in the Model


2.
3. namespace App\Http\Controllers;
4.
Listing 5 shows the LeagueType model. Note the odd archi-
5. use App\Models\LeagueType;
tecture. Business logic is in the Controller, and response 6. use Illuminate\Contracts\Foundation\Application;
structuring is in the Model. 7. use Illuminate\Contracts\View\Factory;
In Listing 5, jsonAPICollection() works like we guessed 8. use Illuminate\Contracts\View\View;
it did by examining the Controller in Listing 2. Method 9. use Illuminate\Http\Request;
jsonAPICollection() loops through the result set, a Laravel 10.
Collection, passing each row to jsonAPI() for formatting. The 11. use function compact;
result is an array of arrays. 12. use function view;
13.
One oddness here is due to Laravel “magic”. Model subclasses 14. class InfraLeagueTypesController extends Controller
use __callStatic()3, along with other magic, allowing calls 15. {
into dynamic protected methods as if they were public 16. public function index(
static methods. 17. Request $request
The important point to note here is that we’re laying out 18. ): Factory|View|Application {
a pattern. The Model subclass does not contain any “model” 19. $data = LeagueType::jsonAPICollection(
code (meaning database-access logic). That logic goes into 20. LeagueType::where(['is_active' => 1])
the Controller. Response formatting is via a method named 21. ->orderBy('id')
22. ->get()
jsonAPI(), which returns an array. Top-level keys are type, ID,
23. );
and optionally an array attributes. More complex responses, 24. $included = [];
especially when generating reports, extend this pattern as 25. return view(
needed. 26. 'api.200',
When we have a result set, rather than a single result, loop 27. compact('included', 'data')
through the results via jsonAPICollection() and return an 28. );
array of arrays. 29. }
30. }

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

14 \ February 2024 \ www.phparch.com


2500 Feet
Transitional Implementation: Part 1

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.

www.phparch.com \ February 2024 \ 15


API Guy

API Essentials: A Developer’s Guide to


Authentication, Validation, and DTOs
Steve McDougall
This article is aimed to walk you through how I handle requests in my API projects—typically
Laravel at the moment. What I do, why I do it, and where I think it could be improved.

tokens based on which device they want to block access from.


There are always many reasons for this and many ways you
can do it! Many people will leverage an IP address for their
API token name, but on a large shared network, different
people may use the same IP address. So, we move to some-
thing more specific: a user agent. However, the problem with
user agents is that people can easily have the same user agent
string if they use the same browser and operating system on
the same versions. Again, on a large network, this is more
likely. So, this leaves us with a question: how can we guar-
antee uniqueness? When dealing with the uniqueness of a
device, we have to leave this to the API client itself, whatever
it is that is integrating with us. In my projects, I typically add
a piece of middleware that I call DeviceIdentifierMiddleware
that will enforce a specific header to be set for later use.
final class DeviceIdentifierMiddleware
{
public function handle(
Working with requests is almost second nature to us at this Request $request,
point. As developers, most of our job involves data, whether Closure $next
it’s handling, saving, validating, fetching, etc. It all stems from ): Response {
if (! $request->hasHeader('X-DEVICE-ID')) {
one place: the humble HTTP request. throw new MissingHeaderException(
If you, like me, have been working with HTTP requests for message: 'Your request requires the ' .
years, then you have likely found a way that works for you. '`X-DEVICE-ID` header to identify itself.',
It has taken me years to solidify what I now do—in every status: Status::BAD_REQUEST,
project. However, I find it is also important to re-evaluate );
}
my approach regularly as the ecosystem is evolving rapidly
in front of us. return $next($request);
Let’s start with the main focus people always talk about: }
Authentication. Authenticate your users so that you can allow }
them access to do what they want to do. There are obviously
multiple ways in which you can authenticate a user on your I set this on every route within my API, even the authenti-
API, and I am going to only go through some of those options cation endpoints. I want to know who is interacting with my
specifically. I will save that for a future article. Instead, let’s API at all times. From a client perspective, a server can set
assume you have a way to authenticate already. How do we a static value, a browser can store something as a cookie or
communicate this to the API? I prefer to leverage the Autho- local storage, and a mobile device or desktop app can access
rization header myself, passed over as a Bearer token. You can, things like the mac address and other unique system prop-
of course, use headers such as API-KEY and X-API-KEY or use erties.
HTTP Basic authentication—but I find that once you find So now that we know that the request coming through is
a solid standard that works for you, stick with it unless you from an identifiable device, and we have checked the authen-
have a reason not to. tication using the Authorization header token. We can move
Typically, these will be API Tokens that are tied to a device onto the next step: Authorization.
and user specifically so that the user can later revoke specific Authorizing users is vital in any application you may be
building. Can this user perform that action? Otherwise,

www.phparch.com \ February 2024 \ 17


API Guy
API Essentials: A Developer’s Guide to Authentication, Validation, and DTOs

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

18 \ February 2024 \ www.phparch.com


API Guy
API Essentials: A Developer’s Guide to Authentication, Validation, and DTOs

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.

Steve McDougall is a conference speaker,


technical writer, and YouTube livestreamer.
You will notice the payload method here, which is respon-
During the day he works on building API
sible for taking that array-focused request data and turning tools for Treblle, and in the evenings spends
it into the ArticlePayload that we want to pass through our most of his time writing content, or contrib-
application to keep context. Typically, this method will type uting to the PHP open source community.
hint the return to an Interface/Contract that all of my Payload Whatever you do, don’t ask him his opinion
objects will implement. This is an extra step I like to take; on twitter/X @JustSteveKing

www.phparch.com \ February 2024 \ 19


Linux Magazine Subscription
Print and digital options
12 issues per year

SubScribE
shop.linuxnewmedia.com

Expand your Linux skills:


• In-depth articles on trending topics,
including Bitcoin, ransomware, cloud
computing, and more!
• How-tos and tutorials on useful tools
that will save you time and protect
your data
• Troubleshooting and optimization tips
• Insightful news on crucial developments
in the world of open source
• Cool projects for Raspberry Pi, Arduino,
and other maker-board systems
Go farther and do more with Linux,
subscribe today and never miss
another issue!

Follow us
@linux_pro Linux Magazine

@linuxpromagazine @linuxmagazine

Need more Linux?


Subscribe free to Linux Update

Our free Linux Update newsletter delivers


insightful articles and tech tips to your
inbox every week.
bit.ly/Linux-Update
Education Station

Symfony UX: Part 2


Chris Tankersly
Despite our best efforts, even back-end developers may be required to write some front-end
code. While the JavaScript community is returning to server-side rendering and returning HTML
instead of JSON, PHP developers find themselves increasingly needing to add interactivity to their
websites and applications.

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

www.phparch.com \ February 2024 \ 21


Education Station
Symfony UX: Part 2
Listing 1.
One quirky thing is that to call methods, you have to use
1. namespace App\Controller;
this.<methodname> instead of just the method name. You 2.
can just use the property name for properties, and it will be 3. use Symfony\Bundle\FrameworkBundle\
output like normal. Now, let’s wire this into a homepage with Controller\AbstractController;
some dummy data: (See Listing 1 and 2) 4. use Symfony\Component\HttpFoundation\Response;
To output our component, we use the component() Twig 5. use Symfony\Component\Routing\Annotation\Route;
function. We pass it the name of our component and the 6.
7. class HomeController extends AbstractController
information we want to pass into the component, and Twig
8. {
will handle moving the data around and calling the appro- 9. #[Route('/', name: 'app_home')]
priate component twig template. While this is much like 10. public function index(): Response
using a Twig include, it provides more type safety as well as 11. {
an easier way to handle logic compared to doing everything 12.
inside a template itself. 13. $articles = [
14. [
Live Components 15. 'title' => 'Article 1',
Live Components allow themselves to be re-rendered 16. 'pubdate' =>
based on actions the user takes. Let’s say we want to show 17. new \DateTimeImmutable('2023-01-01'),
the three newest articles to start, but we also want to give the 18. 'author' => 'Author 1',
19. 'summary' => 'Summary 1',
user a search box where they can enter a query, and we return
20. 'body'=> 'A lot of text',
a list of articles where the title matches their query. We can 21. ],
use a Live Component to pass information back to the server, 22. [
which will re-render just that section of the page. 23. 'title' => 'Article 2',
24. 'pubdate' =>
bin/console make:twig-component --live ArticleSearch 25. new \DateTimeImmutable('2023-01-01'),
26. 'author' => 'Author 2',
Like before, this creates a PHP file to handle our logic and a 27. 'summary' => 'Summary 2',
Twig template. One change is that instead of annotating with 28. 'body'=> 'A lot of text goes here',
#[AsTwigComponent], we now annotate with #[AsLiveCompo- 29. ],
nent] to designate that we want to use this with Stimulus and 30. [
the UX libraries. We also set up a property to hold our query, 31. 'title' => 'Article 3',
$query, which we annotate with #[LiveProp(writable: true)] 32. 'pubdate' =>
to show it will be used on the front end. (See Listing 3, next 33. new \DateTimeImmutable('2023-01-01'),
page) 34. 'author' => 'Author 3',
35. 'summary' => 'Summary 3',
We also create a getArticles() method that will allow us
36. 'body'=> 'A short quip about horses',
to search for a blog article. I moved our static array into a 37. ],
simple repository to help clean up the code that we need to 38. ];
write here. The repository has two helpers, one to return the 39.
newest three articles and one to process our query. There is 40. return $this->render('home/index.html.twig', [
nothing specific there in regards to Symfony UX, but it will let 41. 'controller_name' => 'HomeController',
us reuse the data more easily. (See Listing 4, next page) 42. 'articles' => $articles,
Symfony UX will handle pushing it into the appropriate 43. ]);
44. }
Twig template, as well as wiring $this->query to an HTML
45. }
search box.
Our component needs a visual element. We want to output
a search bar, as well as whatever the component returns. Let’s Listing 2.
make a small template with an input box and our for-loop 1. // templates/home/index.html.twig
from before to re-use our article summary component: 2.
3. {% extends 'base.html.twig' %}
// templates/components/ArticleSearch.html.twig 4.
<div{{ attributes }}> 5. {% block title %}Hello HomeController!{% endblock %}
<input 6.
type="search"
7. {% block body %}
data-model="query" 8.
>
9. <h1>Sample Blog</h1>
10.
{% for article in this.articles %}
{{ component('ArticleSummary', article)}} 11. {% for article in articles %}
{% endfor %} 12. {{ component('ArticleSummary', article)}}
</div> 13. {% endfor %}
14.
15. {% endblock %}
22 \ February 2024 \ www.phparch.com
Education Station
Symfony UX: Part 2

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

www.phparch.com \ February 2024 \ 23


Education Station
Symfony UX: Part 2

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>

24 \ February 2024 \ www.phparch.com


Education Station
Symfony UX: Part 2

If we refresh our homepage, we should now have links to


Listing 8.
our articles, and clicking on them performs a full page refresh
to take us to our article. None of that is unexpected, but we 1. // templates/base.html.twig
can now install Turbo to have the system intercept the clicks 2.
and start to turn the front end into a more single-page-appli- 3. <div class="container">
cation workflow: 4. <div class="header">
We can install Turbo by running: 5. <h1><a href=" {{ path('app_home') }}">App</a></h1>
6. </div>
composer require symfony/ux-turbo 7.
8. <div class="container">
9. <div class="grid">
Then, just refresh our page. As soon you start to move 10. <div class="sidebar">
around on the page, Turbo will watch for link clicks and 11. <ul>
automatically convert them to XHR calls to get new page 12. <li>
13. <a
information. While it looks like the page is refreshed, if you
14. href="{{ path('app_home') }}"
check your network console you will see all the clicks instead 15. data-turbo-frame="content"
request the new pages via XHR, and Turbo is just replacing 16. >Home</a></li>
the DOM. 17. <li>
This is not a huge performance benefit by itself; the big win 18. <a
is that the entire page is now persistent. As we move from 19. href="{{ path('app_view_all_articles') }}"
page to page, you no longer have page loads that will reset the 20. data-turbo-frame="content"
state of your JavaScript. The content may change as you move, 21. >Articles</a></li>
but your JavaScript code will work across page refreshes. 22. </ul>
23. </div>
The flip side of this is your code needs to understand this.
24. <div class="content">
You may need to move your script tags to the base layout or 25. <!-- Main content goes here -->
to the <head> tag so that they are not included in every refresh 26. <turbo-frame id="content">
since Turbo will reload any script tags inside the body. You 27. {% block body %}{% endblock %}
will also want to set the defer tag on any script tags so they do 28. </turbo-frame>
not block page execution. 29. </div>
30. </div>
Turbo Frames 31. </div>
Once Turbo is enabled, you can start to carve your 32. </div>
application pages up into Frames. Frames allow you to
compartmentalize navigation and interactions with multiple content turbo frame with whatever comes back. Since this is
items on your page. It allows the system to replace frames via a simple blog, everything visually acts as we expect, but only
navigation or actions without touching the rest of the page. the “content” section of the DOM now updates when the user
In a way, think of the current setup we have as one massive moves between pages.
frame. While we are not getting a full page refresh, the entire Turbo frames can also take a src attribute with a URL.
content of the page is being replaced as we move around, This will cause that turbo frame to be lazy-loaded. We could
including the header section. If the “View Article” twig have the comments on an article load only after the element
template extends from a different layout than the home- becomes visible to reduce the load on the server. Let’s update
page, you will get the layout change as you move. It is all just our View Article template to do just that: (See Listing 9)
captured and rendered via XHR events.
Let’s start off simple. Let’s first add a sidebar with a few links Listing 9.
to stuff and put the main body content into a turbo frame:
1. // templates/view_articles/index.html.twig
(See Listing 8)
2. <div>
We added a new element, <turbo-frame>, around the body
3. <h2>{{article.title}}</h2>
block. This will start to have the content sectioned off from 4. <h6>By {{article.author}}</h6>
the main application, and links inside this “turbo frame” will 5. <div>{{article.body}}</div>
only refresh this section of the page. We also added a new 6.
attribute to the sidebar links called data-turbo-frame. This 7. <turbo-frame
lets us send the result of a click to another turbo frame. 8. id="comments"
app_view_all_articles is nothing more than a route that 9. src="{{ path('comments',{slug:article.slug}) }}"
dumps out every article using the ArticleSummary component. 10. >
11. </turbo-frame>
If a user clicks on any sidebar link, the XHR request is sent
12. </div>
to the server, but the system will replace the contents of the

www.phparch.com \ February 2024 \ 25


Education Station
Symfony UX: Part 2

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/

One More Thing


Not to Steve Jobs, but there is one more thing I want to Chris Tankersley is a husband, father, author,
show off with Symfony UX: Turbo Streams. We will take a speaker, podcast host, and PHP developer.
look at that next month since we will be diving into Mecure8, Chris has worked with many different frame-
works and languages throughout his twelve
which is a protocol used for asynchronous communication
years of programming but spends most of
that uses Server Side Events to push changes to the client.
his day working in PHP and Python. He
is the author of Docker for Developers and
7 https://ux.symfony.com works with companies and developers for
8 https://symfony.com/doc/current/mercure.html integrating containers into their workflows.

Docker For Developers is designed for developers looking at


n Northwest Ohio
ore than ten years
. He
, across
Docker as a replacement for development environments like
virtualization, or devops people who want to see how to take
to
eeds. In addition
ts for
erver deploymen
ng
st what basic hosti
Sculpin,
me working with

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

This revised and expanded edition includes:


Docker for Develo

• Creating custom images


• Working with Docker Compose and Docker Machine
pers,
2nd Edition

• Managing logs
• 12-factor applications
ey
Chris Tankersl
Tankersley

Order Your Copy


https://phpa.me/docker-devs

26 \ February 2024 \ www.phparch.com


PHP Puzzles

Five Card Stud


Oscar Merida
Last month, we solved how to model and shuffle a deck of cards. In this article, we’ll look at how to
identify the poker hands we deal out.

identifying a full house and dealt a straight, we’d have to run


Recap our code again and hope the RNG gods give us what we need.
We need some way to control the inputs. By doing so, we
For next month, now that we have a way to shuffle a deck can work on a particular hand one at a time. What allows us
of cards, let’s put it to use. Shuffle a standard poker deck to do that? Unit testing. I’ll use PHPUnit to set up a particular
of cards and deal out four hands of 5 cards each. For each hand and then write the code for each hand. I’ll show how
hand, identify all the valid poker hands it contains. to do that for two hands and put the complete solution on
GitHub2 and this issue’s code archive.
Bonus points: identify which hand has the winning hand.

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.

Royal flush Same suit, 10 to Ace Listing 1.


Straight flush Same suit, sequential 1. <?php
Four of a Kind Four cards of the same number 2.
3. namespace Puzzles;
Three cards of the same value, two 4.
Full House
of another value 5. use Puzzles\Cards\Card;
6.
Five cards of the same suit, that
Flush 7. class Hand
are not sequential 8. {
Straight Five sequential cards of any suit 9. public array $cards = [];
10.
Three of a kind Three cards of the same value 11. public function __construct(Card ...$cards)
Two sets of two cards of the same 12. {
Two Pair 13. $this->cards = $cards;
value
14. }
One set of two cards of the same 15.
One Pair
value 16. /**
17. * @return Card[]
High Card Any other configuration of cards 18. */
19. public function getCards(): array
20. {
return $this->cards;
Planning Our Approach
21.
22. }
We’ll be shuffling cards and making hands of five cards, 23. }
but working with random results on every run is problem-
atic. Each time we run, we may not get the input of cards we
need to test what kind of hand we have. If we’re working on

1 https://phpa.me/wikipedia
2 https://github.com/omerida/PokerHands

www.phparch.com \ February 2024 \ 27


PHP Puzzles
Five Card Stud

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

28 \ February 2024 \ www.phparch.com


PHP Puzzles
Five Card Stud

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. }

www.phparch.com \ February 2024 \ 29


Readable Code

Is Your Code Documented Enough To


Help?
Christopher Miller
So Far In The Series, we’ve looked at Planning, Abstraction, Encapsulation, and Testing. That’s
a good place to start—the last major section to set out our groundwork is the dreaded thing of
Documentation. As developers, we’re naturally divided into a dichotomy of documentation—we
either love it and document all the things or hate it and document none of the things. I advocate
for a place in the middle—Document the things we need documented, not the things we don’t.

Now, in this case, it took me a long time to understand


what was happening; the variable naming also made it more
complicated, as did the lack of typing on variables and returns.

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

Over Documenting: a Story 14.


15.
*
* @link ...
I was working on a large codebase that was massively 16. *
over-documented a while back. Every single thing was docu- 17. * @param float $percentage the percentage to ...
18. * @param float $value the value to use for ...
mented to the nth degree. It made it incredibly difficult to
19. *
read the code, as there was just so much being documented 20. * @return float the calculated value of perc...
in there! Here’s an example of a class documented that way: 21. */
(See Listing 1) 22. public function calculatePercentageOfValue(
23. float $percentage, // the percentage to use...
As I’m sure you can see, this massively extends the func- 24. float $value // the value to use for the ...
tionality and makes it complicated to read what’s really 25. ): float // the calculated value of percentage ...
happening, as the documentation is too complex and the 26. {
setup is confusing. Well, every single class, method, and file 27. // Divides the percentage by one hundred
was documented to this extent. The codebase was overly 28. $percentageDividedByOneHundred=$percentage/100;
29.
complex to understand and really difficult to work with. 30. // multiplies the percentage divided by one...
31. $percentageDividedByOneHundredMultipliedByValue
Under Documenting: a Story 32.
33.
= $percentageDividedByOneHundred * $value;

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. }

30 \ February 2024 \ www.phparch.com


Readable Code
Is Your Code Documented Enough To Help?

Listing 2. We can now understand better what’s happening here, and


we could even add an @link to the method for an explanation.
1. <?php
2.
So, as you can now see, too much documentation is bad—but
3. class ComplicatedMath { so is not enough. That begs the question: What should we
4. public function calc($n) { document?
5. $sequence = [];
6.
7. while ($n != 1) {
What Should We Document?
8. $sequence[] = $n; For me, there are 5 types of documentation:
9.
10. if ($n % 2 == 0) { 1. Setup Documentation—Tell me how to set up the
11. $n = $n / 2; application when I download it
12. } else { 2. Documentation By Inference—Don’t tell me what I
13. $n = 3 * $n + 1; already know from the setup—You don’t need to give
14. } me @param in the docblock if the method tells me my
15. }
16.
input is int
17. $sequence[] = 1; 3. “Why” Documentation—Tell me WHY something
18. happens, if there’s a quirk that I might need to know
19. return $sequence; about
20. }
4. “What” Documentation—Tell me WHAT is happen-
21. }
ing, if it’s complicated to understand from the flow
(better still, if you can, abstract it!)
As it happens, this is actually the Collatz conjecture (https://
phpa.me/collatzproblem1 . What we’re seeing is a naive 5. “How” Documentation—Tell me HOW to use some-
implementation of the solution. The same class with some thing, if it’s not entirely clear from the setup
documentation that’s helpful (and a name change) makes this
These five will help you balance your setup nicely for docu-
easier to understand: (See Listing 3)
mentation and give you a strong starting place to work from.
So, let’s start at the top:
Listing 3.
1. <?php
2.
Setup Documentation
3. class ComplicatedMath { Setting up documentation should give me exactly the steps
4. public function collatzCalculation(int $n): array I need to get started with the application for local develop-
5. { ment. That is where I use this particular framework for a
6. $sequence = []; README.md2 file. (Listing X has been shortened for space)
7.
(See Listing 4 on the next page)
8. // 1 is our root value
9. // once we reduce to 1,we should stop
10. while ($n != 1) {
Anyone that’s been around Drupal will probably recognize
11. // add the current value to the sequence this! It’s from Drupal’s guide on writing a README found
12. $sequence[] = $n; at https://phpa.me/drupal-readme3 and does a really good
13. job of giving you a setup starting point—but, it doesn’t go
14. if ($n % 2 == 0) { deep enough on WHAT to include in the Installation section
15. $n = $n / 2; // if even, divide by two for my liking. What we actually need is a step-by-step guide,
16. } else { assuming nothing we need is installed other than the base
17. // otherwise multiply by 3 and add one
programming language and GIT. Here’s my quick cheat sheet
18. $n = 3 * $n + 1;
19. }
of questions about what to put in the Installation Section:
20. } 1. What software do I need to do this, other than the
21. language, git and editor?
22. // outside of our loop
23. // add the base value to the sequence 2. Are there any specialist setups I need before I can
24. $sequence[] = 1; download the repository?
25. 3. Are there any specialist instructions I need to set up
26. return $sequence; the environment once I have downloaded the repos-
27. }
itory?
28. }
2 http://readme.md/
1 https://phpa.me/collatzproblem 3 https://phpa.me/drupal-readme

www.phparch.com \ February 2024 \ 31


Readable Code
Is Your Code Documented Enough To Help?

4. What are the step-by-step, line-by-line instructions Documentation by Inference


for getting this set up? Previously in this series, we’ve touched on encapsulation,
5. Are there any specialist instructions I need to follow abstraction, and naming—well, this is why! If you get the
after I set it up for the first time? balance right with these, we can actually avoid the need to
document a lot of stuff. Imagine, if you will, the two following
By answering those questions in the order listed, we can things (obviously over-simplified for the purpose of this
safely assume we have a full and helpful guide on how to set article, but you will understand the point) (See Listing 5)
up the project.
Listing 5.
Listing 4.
1. <?php
1. # Example Template 2.
2. 3. /**
3. The introduction summarizes the purpose and function 4. * adds two integer numbers together
4. of the project and should be concise (a brief 5. *
5. paragraph or two). This introduction may be the same 6. * @param int $a
6. as the first paragraph on the project page. 7. * @param int $b
7. 8. *
8. For a full description of the module, visit the 9. * @return int
9. project page^[<https://www.drupal.org/...>]. 10. */
10.
11. function f($a,$b){return $a+$b;};
11. Submit bug reports and feature suggestions, or ... 12.
12. issue queue^[<<https://www.drupal.org/project... 13.
13. 14. function addTwoNumbers(
14. ## Table of contents (optional>] 15. int $numberOne, int $numberTwo
15. - Requirements 16. ): int {
16. - Recommended modules 17. return $numberOne + $numberTwo;
17. - Installation 18. }
18. - Configuration
19. - Troubleshooting
20. - FAQ The functionality is identical here, but the content of the
21. - Maintainers function documents itself. The fabled “self-documenting
22. code”. We don’t need to have a doc block if the code is clear
23. ## Requirements (required) and concise. Don’t tell me what I can clearly see from the
24. This module requires the following modules:
code, but do tell me what I can’t see from it!
25. - Bad judgment^[<<https://www.drupal.org/...
26. OR
Self-documentation is quite a complicated idea, and many
27. This module requires no modules outside of Drupal core. people find it a misnomer—going against the concept of
28. documentation. I personally sit on the side of it’s helpful
29. ## Recommended modules (optional>] to ensure your code reads like English—left to right, top to
30. ## Installation (required, unless a separate ... bottom. Ensure that it’s simple to understand. Ensure it’s easy
31. to find the bit you want. You wouldn’t want to read a book
32. Install as you would normally install a contributed
with 20 footnotes for each page, now would you? So why
33. Drupal module. For further information, see ...
34. would you want the same thing in your code—you wouldn’t!
35. ## Configuration (required)
36.
37.
1. Enable the module at Administration > Extend.
2. Profit.
“Why” Documentation
38. Telling me WHY something happens is really, really helpful,
39. ## Troubleshooting (optional) especially if it seems out of place. Let’s take an abstract
40. example here—again, created purposefully for this article,
41. ## FAQ (optional) and is not how I would implement this in real life: (See Listing
42. **Q: How do I write a module README?**
6 on the next page)
43. **A:** Follow this template. It's fun and easy!
44. “That last line there is quite odd, isn’t it? Why are we forget-
45. ## Maintainers (optional) ting a cache here? We’re not caching anything new!” Those
46. - Dries Buytaert - ... are the sorts of thoughts in my head when looking at it—“it’s
not the greatest, is it? Let’s just delete that line—it’s not
needed”. So, as a developer, I judiciously remove an unneeded
line. Except, now I am being asked why our queries have gone
wrong. There are thousands of stock queries that are returning

32 \ February 2024 \ www.phparch.com


Readable Code
Is Your Code Documented Enough To Help?

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

www.phparch.com \ February 2024 \ 33


Readable Code
Is Your Code Documented Enough To Help?

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. }

34 \ February 2024 \ www.phparch.com


Readable Code
Is Your Code Documented Enough To Help?

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. }

www.phparch.com \ February 2024 \ 35


Readable Code
Is Your Code Documented Enough To Help?

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

Tackle Any Coding Challenge


With Confidence
This book teaches the skills and mental
processes these challenges target. You
won’t just learn “how to learn,” you’ll
learn how to think like a computer.

Order Your Copy


https://phpa.me/fizzbuzz-book

36 \ February 2024 \ www.phparch.com


Security Corner

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

www.phparch.com \ February 2024 \ 37


Security Corner
Cheating is Encouraged

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

Secure your applications against


vulnerabilities exploited by attackers.
Security is an ongoing process not something to add right
before your app launches. In this book, you’ll learn how
to write secure PHP applications from first principles.
Why wait until your site is attacked or your data is
breached? Prevent your exposure by being aware of the
ways a malicious user might hijack your web site or API.

Order Your Copy


https://phpa.me/security-principles

38 \ February 2024 \ www.phparch.com


Barrier-Free Bytes

Using Different Browsers


Maxwell Ivey
I have often said that the lack of accessibility isn’t the result Just imagine how it would make you feel that they don’t
of any malice by business owners, site designers, or code understand you and haven’t even tried to understand you.
developers. Simply put, they just don’t realize the effects of This is what happens when you ask someone who depends
their decisions. Or, the other possibility is that they just don’t on a screen reader, a screen magnification system, etc. And
feel they have the time to incorporate accessibility given chal- because people with disabilities tend to avoid communicating
lenging deadlines and limitations of people, time, and money. about their disabilities, you most certainly will have such visi-
So, when I run across something where I feel a lack of tors to the sites you are building whether you know it or not.
understanding is causing unnecessary problems for people Do you really want to risk alienating users because it’s
with disabilities, I think the best thing is to help them under- easier?
stand why the problem exists. I have recently come to understand that there are some
I was recently trying to use a new scheduling website to problems between ARIA and Safari that will tempt people to
make an appointment to be interviewed for a podcast. The push toward Chrome or Firefox. In fact, I had to help one of
name of the app is irrelevant. The important thing is that my clients this week sort out an issue caused by this lack of
when I expressed my frustration to the podcast owner, his communication between the two languages, I guess you call
first response was have you tried it in a different browser. them.
This is not uncommon. Many more sites lately start with But this challenge shouldn’t be an excuse to eliminate
a heading or an announcement that says this site will only support for Safari. It should be seen as an opportunity to
run in Firefox or Chrome. Or it will display that this site will stand out. You could be one of those companies that makes
perform better in one of those browsers. an effort and tout your accessibility on the major platforms
On the surface, this may not sound like a big deal. However, with the major screen readers, for example.
this is a very big request for an adaptive technology user like I know I have spoken with rather passionate language here.
myself who depends on a screen reader. But it aggravates me to try to accomplish even the simplest
This is especially true for Mac users but applies to a certain tasks using a browser where I have to hunt up keyboard
level for Microsoft users running Jaws for Windows or NVDA. commands or even ask Google or blind friends how to get
You see, a screen reader must depend on someone to write that task done.
the code to understand what is happening in a given app or But before I go, I want to encourage you a little. I have also
website. said before that accessibility is a process or a journey. If your
For Mac users, the most valuable part of the screen reader team is challenged with Safari in the short term, how about
VoiceOver is that it is built into the operating system. When posting something that says, for right now, this site will run
Apple updates its OS, it updates the accessibility options as better on Firefox or Chrome? Still, we appreciate and value
well. They may sometimes break accessibility in the short users of Safari and are working diligently to make this site
term, but they are in a better position to fix the accessibility workable and accessible here, too. Then, you at least tell those
issues caused by a significant update quickly. disabled people who visit the site you built that you have
Because of this, most Mac users are Safari users. thought about them. And at the end of the day, that’s all we
Besides the differences between using a home-grown app really want. We want to feel like people care about us, want us
and a third-party app, there is the issue of familiarity. to be able to work and play, too, and will make just as strong
I’m sure most of you have heard of muscle memory. I an effort to include us on our favorite platforms as anyone
imagine each of you has certain apps you use all the time and else online. I look forward to hearing your thoughts. And
can operate without even thinking about it. I bet you even I’m available to answer any questions. Thanks so much for
have certain furniture set to your body style or a computer listening, Max
and office setup that you get disturbed by if someone moves
your stuff. Maxwell Ivey is known around the world as
So, imagine someone who is working harder than a The Blind Blogger. He has gone from failed
non-disabled person trying to accomplish tasks online and carnival owner to respected amusement
keep up with their coworkers and competitors, and then equipment broker to award-winning author,
motivational speaker, online media publicist,
someone comes along and says, “Hey, use a different browser
and host of the What’s Your Excuse podcast.
that you don’t use every day.”!
@maxwellivey
And imagine they say it as if they don’t realize the hardship
they may be causing by making this request and/or demand.

www.phparch.com \ February 2024 \ 39


Real SolutionS
r
foReal netwoRkS

ADMIN is your source


for technical solutions
to real-world problems.

Improve your admin


skills with practical
articles on:
• Security
• Cloud computing
• DevOps
• HPC
• Storage and more!

Get it
FASt
with a digital
subscription!

@adminmagazine @adminmag 6 issues per year!


Order NOW
ADMIN magazine @adminmagazine shop.linuxnewmedia.com
PSR Pickup

PSRs In Action: PHP League Event


Package
Frank Wallen
An Event Dispatcher is a well-used and battle-tested design principle where developers can easily
inject functionality into an application, especially legacy applications.By dispatching events in
your application, you can dramatically reduce the complexity of refactoring your code.

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)

$dispatcher = new \League\Event\EventDispatcher(); Listing 1.


$dispatcher->subscribeTo($eventIdentifier, $listener);
$dispatcher->dispatch($event); 1. class SimpleEvent
2. {
When instantiating the dispatcher, by default, it creates an 3. public function__construct(
internal listener registry, but one can also define their own 4. public string $thingId,
as long as the Psr\EventDispatcher\ListenerProviderInstance 5. public string $thingData
is implemented. The default listener registry is League\Event\ 6. ) {}
PrioritizedListenerRegistry. One of the reasons for creating 7. }
8.
a custom listener registry is sorting (hinted at by the name of
9. $event = new SimpleEvent('A155', 'Some information.');
10.
1 https://thephpleague.com 11. //As a stdObject
12. $event = (object)[
2 https://event.thephpleague.com/3.0
13. 'thingId' => 'A155',
3 https://www.php-fig.org/psr/psr-14 14. 'thingData' => 'Some information.',
4 https://github.com/thephpleague/event 15. ];

www.phparch.com \ February 2024 \ 41


PSR Pickup
PSRs In Action: PHP League Event Package

Keep events clean and simple; they are primarily a data


Listing 3.
transfer object, leaving the work to the listener. Note that
subscribing a listener to an event requires an $eventIdentifier. 1. use \League\Event\HasEventName
This is really just a string that represents the event. A popular 2. class SimpleEvent implements HasEventName
style in libraries and frameworks is to use a fully qualified class 3. {
path for the id: subscribeTo(SimpleEvent::class, $listener), 4. public function__construct(
but it is up to the developer to decide. 5. public string $thingId,
6. public string $thingData
The minimum requirements of the listener is that it be call-
7. ) {}
able and accepts the event object. That means that it can be a 8.
class object or even a function. For a class, the Event package 9. public function eventName(): string
provides a Listener interface for ease. (See Listing 2) 10. {
11. return 'event.simple';
Listing 2. 12. }
13. }
1. use \League\Event\Listener; 14.
2. 15. $dispatcher->subscribeTo(
3. class SimpleListener implements Listener 16. 'event.simple',
4. { 17. $trackerListener
5. public function __invoke( 18. );
6. object|SimpleEvent $event 19. $dispatcher->subscribeTo(
7. ): void { 20. 'event.simple',
8. //do something with $event 21. $notificationListener
9. } 22. );
10. } 23.
11. 24. $dispatcher->dispatch(
12. $dispatcher->subscribeTo( 25. new SimpleEvent('A155', 'Some information.')
13. $eventIdentifier, 26. );
14. new SimpleListener()
15. );
16. Listing 4.
17. //Using an anonymous function
18. $dispatcher->subscribeTo($eventIdentifier, 1. use League\Event\ListenerSubscriber;
19. function(object|SimpleEvent $event) { 2. use League\Event\ListenerRegistry;
3.
20. //do something with $event
21. }); 4. class ListenerProvider implements ListenerSubscriber
5. {
6. public function __construct(protected array $listeners)
7. {}
So far, we have seen the most basic functionality of Event 8.
and its adherence to PSR-14. It offers more than that with 9. public function subscribeListeners(
some expanded usage and utilities. I stated before that an 10. ListenerRegistry $registry
event should be clean and simple, but Event offers some addi- 11. ): void {
tional sugar in the form of the HasEventname interface, which 12. foreach ($this->listeners as
requires that the event class has an eventName() method. The 13. $eventIdentifier => $listenerClasses) {
event could return a string, like event.simple, so you can 14. foreach ($listenerClasses as $listenerClass) {
register a listener by an event name rather than a specific 15. $registry->subscribeTo(
16. $eventIdentifier, new $listenerClass
identifier. Several events could return the same id to trigger
17. );
the same listener. Assume we have the same event class from 18. }
the previous examples: SimpleEvent. We make the following 19. }
changes: (See Listing 3) 20. }
Both listeners here will have received the event.simple 21. }
event. 22. $listeners = [
In the PSR-14 interfaces, an EventDispatcher accepts a 23. 'event.simple' => [
ListenerProviderInterface object that will provide the listener 24. SimpleListener::class,
registry. The Event package offers a helpful utility: League\ 25. LogListener::class,
26. ],
Event\ListenerSubscriber. Using this interface, a custom
27. 'event.issue' => [LogListener::class,],
ListenerProvider can group registration by concern or by
28. ];
groupings that make sense. Consider the following: (See 29. $dispatcher->subscribeListenersFrom(
Listing 4) 30. new ListenerProvider($listeners)
31. );

42 \ February 2024 \ www.phparch.com


PSR Pickup
PSRs In Action: PHP League Event Package

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/

What if there’s no API?


Web scraping is a time-honored
technique for collecting the information
you need from a web page. In this book,
you’ll learn the various tools and libraries
available in PHP to retrieve, parse, and
extract data from HTML.

Order Your Copy


https://phpa.me/web-scraping-2ed

www.phparch.com \ February 2024 \ 43


RADAR

A Comparative Analysis of Swoole vs


Roadrunner
Matt Lantz
PHP’s performance has picked up considerably over the last five or so years, but it became more
impressive with the introduction of Swoole and Roadrunner. These two PHP servers serve a similar
purpose, providing high-performance web applications. We’ll thoroughly examine Swoole and
Roadrunner, exploring their functionalities, advantages, and challenges to help PHP developers
determine which best suits their needs. However, let’s begin with a brief review of the current
standard approaches for most PHP-based applications.

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

44 \ February 2024 \ www.phparch.com


RADAR
A Comparative Analysis of Swoole vs Roadrunner

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

Harness the power of the Laravel


ecosystem to bring your idea to life.
Written by Laravel and PHP professional
Michael Akopov, this book provides a concise
guide for taking your software from an idea
to a business. Focus on what really matters to
make your idea stand out without wasting time
on already-solved problems.

Order Your Copy


https://phpa.me/beyond-laravel

www.phparch.com \ February 2024 \ 45


finally{}

gs
You g Thin
w
Kne
ettin
g
For
What was I thinking?
Did I Do This?

Dear Past Me, What Were You Thinking?


Beth Tucker Long
We have all heard the joking anecdotes about how someone This kind of information can really affect the way a business
can’t figure out a piece of code, they wonder who on Earth approaches a new development. Perhaps next time you are
wrote such confusing code, and then the punchline is, it was working on Feature X in the code, you want to remember to
them. These anecdotal stories usually precede a warning or clean up the formatting of the code but also see if you could
recommendation about using comments in your code to add in Feature Y because it would help out the marketing
document what you have done. While I think that is a good department’s annual spring campaign. Or perhaps you
idea, I don’t think code comments are really the answer. wanted to build Feature X in a more straight-forward way,
There are a lot of great tools out there that allow you to but when you removed a seemingly dead section of the code,
add nice formatting to your code comments and then parse Feature B stopped working and no one had time to figure out
them automatically into a spiffy “documentation” website. why, so you left the dead code there and future you should
This means you can write up your documentation while definitely not touch that section of code unless you have time
you are in working on each file. At what point, though, do to debug why Feature B won’t work without it. Or perhaps
the comments take up more room than the actual code? you wanted to just use version 3.5.2 of Awesome Library 2000,
How much outside knowledge can (and should) your code but there’s an API that two of your biggest customers use that
comments really hold? doesn’t currently work with any version above 3.5.1 so you
It’s great to have the information you need to edit the code can’t upgrade it yet.
in a comment right where the code is. Things like expecta- This is critical information for your documentation but it is
tions for parameters and output along with brief descriptions not information that belongs in the code. Next time you are
of basic functionality and what this particular block of code making changes and find yourself justifying a non-standard
needs to interact with can be incredibly helpful when you just way of addressing the problem, take a look at your docu-
need to pop in to some unfamiliar code and make a quick mentation system. If you are only capturing the code-centric
change. information, find a way to integrate the business knowledge
What if you are making a significant change to a bigger that influenced your non-standard decision. Future you will
piece of the code? Are those brief details going to be enough thank you for it.
to help you make an informed decision on the best way to
make the updates or implement new functionality? Not likely. Beth Tucker Long is a developer and owner
At that point, you need to know more of the business logic at Treeline Design, a web development
side of things: company, and runs Exploricon, a gaming
convention, along with her husband, Chris.
• Which department needed this feature built? She leads the Madison Web Design & Devel-
• What business needs constrained the way this was opment and Full Stack Madison user groups.
built? You can find her on her blog (http://www.
alittleofboth.com) or on Twitter @e3BethT
• What other technology did this interact with?
• What would you have done differently if you had more
time, money, etc.

46 \ February 2024 \ www.phparch.com

You might also like