0% found this document useful (0 votes)
45 views44 pages

Phparchitect 2024 07

The July 2024 issue of php[architect] focuses on enhancing PHP development practices, featuring articles on advanced search techniques for Laravel applications, security measures, and user workflow improvements. Key topics include full-text search implementation, dependency injection, and maintaining backward compatibility in PHP. The magazine emphasizes continuous education and community engagement to foster high-quality coding standards.

Uploaded by

marek.rode
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
45 views44 pages

Phparchitect 2024 07

The July 2024 issue of php[architect] focuses on enhancing PHP development practices, featuring articles on advanced search techniques for Laravel applications, security measures, and user workflow improvements. Key topics include full-text search implementation, dependency injection, and maintaining backward compatibility in PHP. The magazine emphasizes continuous education and community engagement to foster high-quality coding standards.

Uploaded by

marek.rode
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 44

JULY 2024

www.phparch.com Volume 23 - Issue 7

The Magazine for PHP Developers

SEARCH FOR
GOOD CODE
Advanced Search For Laravel Applications

Also Inside:
         ƒ ƒ   
   † 
    '  
­  €  ‚ƒ   ƒ 
   
­ ƒ   ‚
„
a php[architect] guide

Learn how to build dynamic and secure websites.


The book also walks you through building a typical Create-Read-
Update-Delete (CRUD) application. Along the way, you’ll get solid,
practical advice on how to add authentication, handle file uploads,
safely store passwords, application security, and more.
Available in Print+Digital and Digital Editions.

Purchase Your Copy


https://phpa.me/php-development-book
JULY 2024
Volume 23 - Issue 7

2 Search For Good Code 27 PHP & WebAssembly: An Edge


Case
3 Advanced Search For Laravel RADAR
Applications Matt Lantz
César Augusto Méndez Vargas

9 PHP Under Attack 29 PHPStan’s Watchful Eye


Security Corner PHP Foundry
Eric Mann Oscar Merida

11 User Workflow Improvement 35 Securing PHP Applications:


with Hedy Lamarr - Authentication And
2500 Feet Authorization
Edward Barnard Readable Code
Christopher Miller
15 Backward Compatibility
Education Station 42 Driving Abandonment
Chris Tankersly finally{}
Beth Tucker Long
21 Understanding and
Implementing a Dependency
Injection Container - Part II
Level Up
Wendell Adriel

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

Write For Us
Search For Good Code If you would like to contribute,
contact us, and one of our editors
As PHP Architects, our relentless a comprehensive approach, delving will be happy to help you hone
pursuit of high-quality code is always at into unit testing, semantic versioning, your idea and turn it into a beauti-
the forefront of our work. We take pride method/function signature changes, and ful article for our magazine.
in assuring our clients that the applica- dependency updates, fostering a deeper Visit https://phpa.me/write
tions we build are not meant to confine understanding of this critical topic. or contact our editorial team
them. At the same time, we value the Edward Barnard’s captivating piece, at [email protected] and get
opportunity to cultivate long-standing ‘User Workflow Improvement with Hedy started!
partnerships with our clients, many of Lemarr,’ offers a fascinating glimpse from
whom we consider to be exceptional 2500 Feet into the world of program-
business collaborators. mers’ minds through the lens of another Stay in Touch
We also understand that some compa- language, providing an enriching and Don't miss out on conference,
nies want to hire a company to build the thought-provoking read. book, and special announcments.
initial proof of concept and then bring In the Security Corner, Eric Mann Make sure you're connected with
the development in-house. Good code continues to enlighten us with ‘PHP us.
and standard coding practices help us Under Attack’, dispelling misconceptions • Subscribe to our list:
achieve this. Don’t let yourself fall into about PHP’s security and highlighting its https://phpa.me/sub-to-updates
the trap that you code in a way to achieve significant advancements over the years, • Mastodon:
job security. I’ve seen too many instances nurturing a sense of confidence in the @[email protected]
where developers tried that, only to be language’s robustness.
• Twitter: @phparch
out of work because of how they acted Christopher Miller’s insightful explora-
towards the project. tion of Authentication and Authorization • Facebook:
Continuous education serves as the in the ongoing “Securing PHP Applica- http://facebook.com/phparch
cornerstone of upholding exemplary tions” series promises to further fortify
coding practices. Active engagement our understanding of essential security
within the community allows us to culti- measures.
vate a network of professionals from With an optimistic outlook, Matt Lantz
whom we can seek assistance when introduces us to the cutting-edge tech-
necessary. With these principles in mind, nology of WebAssembly in the RADAR
let’s approach this month’s challenges column, demonstrating its potential
with renewed determination and a focus through ‘PHP & WebAssembly: An Edge
on the two key concepts outlined. Case,’ staying true to his commitment to
Now, let’s embark on an exciting keeping us abreast of the latest advance-
journey into this month’s issue, embracing ments.
the wealth of knowledge awaiting us. Oscar Merida shares invaluable insights
The highlight of this edition is the on maintaining the robustness of our
compelling article penned by Cesar applications with ‘PHPStan’s Watchful
Augusto Mendez Vargas, titled ‘Advanced Eye,’ empowering us to proactively safe-
Search For Laravel Applications.’ Cesar guard our systems against potential bugs.
adeptly illustrates how to integrate Wendell Adriel’s comprehensive guide,
full-text search capabilities into your ‘Understanding and Implementing a
application, offering invaluable insights Dependency Injection Container—Part
and practical guidance. Delving into II,’ promises to elevate our expertise
segmentation, normalization, filters, and nurture our skills, enabling us to
conditions, and more, this article prom- construct a robust Dependency Injection
ises to equip you and your team with a system from the ground up.
powerful tool for enhancing application Lastly, Beth Tucker Long provides a
functionality. compelling perspective on the evolving
In the Education Station, Chris status of PHP in ‘Driving Abandonment,’
Tankersly passionately advocates for offering a thoughtful and insightful Download the Code
preserving ‘Backward Compatibility’, outlook on the language’s trajectory. Archive:
shedding light on its paramount signifi- This month’s lineup is a treasure trove
https://phpa.me/July2024_code
cance. Chris skillfully navigates through of knowledge and inspiration, inviting us
the intricacies, emphasizing the adverse all to explore, learn, and grow.
impacts of breaking userspace. He takes
FEATURE

Advanced Search For Laravel Applications


César Augusto Méndez Vargas
Have you got in a scenario where you are dealing with slow results, searches are becoming
a problem, you are having a huge amount of “LIKE” and “OR” operators, or simply, you think
filtering your data is a little bit complicated? Don’t worry; you are in the right place. In this
article, we will show you how to implement Full-Text Search on your application and how to
add several functionalities to improve your searching experience.

After processing, this is how the inverted index will look


Introduction like: (See Figure 2)
Before diving into deeper waters, let’s briefly introduce you
to full-text search. Figure 1.
Compared to a database query, in which texts are compared
character by character, a text search engine uses a token,
which is the basic unit for finding a match between the query
and records.
Tokens are obtained in a process called tokenization,
and perhaps it may vary from one engine to another; it is
composed of the following steps:

Segmentation and Normalization


Segmentation is splitting a text into smaller units called
tokens; this process can be complex based on the rules specific
to each language (English, Spanish, French, Mandarin, etc.)
of the text, but, to keep things simple, let’s use the most basic
rule, which is splitting a text on each white space. As a result,
this text: “I love dragon fruit”, will be transformed into the
following array of tokens: [“I”, “love”, “dragon”, “fruit”].
The next step is normalization, which also varies based on
the rules of the language (English, Spanish, Mandarin, etc.).
For example, if we take Spanish, it would remove accent marks
and lowercase each token. As a result, this text, “Participé en
TEK 2023”, will be transformed into the following array of Figure 2.
tokens: [“participe”, “en”, “tek”, 2023].
After tokens are generated, they need to be stored some-
where, and the way things are stored is where the text search
engine shines.
Among the multiple ways of storing tokens, we will focus
on one: “Inverted index”. Keep in mind that text engines have
and do use multiple data structures, and understanding how
they work and their use case is important for better results.
Back to the inverted index, after the tokenization process
is done, each token is stored in an index that references the
documents (text) it belongs to. This allows you to search for
tokens instead of diving into each document individually.
For a better understanding, please refer to this visual repre-
sentation.
Let’s say we have two documents stored in our search
engine database: (See Figure 1)

www.phparch.com \ July 2024 \ 3


Advanced Search For Laravel Applications

As we can see, from now on, instead of looking for each Please note that the processing time to create/update a
token on each document, we can go to our inverted index, see filter will vary depending on your database size.
which documents have the tokens of our search, and return Now that you have created your filters, you can perform
them accordingly. searches on the data with more complex queries; this is done
After gaining some basic understanding of how full-text thanks to the following:
search works, let’s look at some useful functionalities. Each Conditions
topic can be a book on its own, but after covering the basics,
you will be able to integrate the most used parts of your app. Conditions are a filter’s basic building blocks. They are
written in theattribute OPERATOR valueformat, where:

Filter • attribute is the attribute of the field you want to filter


on
The magic of any database is defining your structure. In
• OPERATOR can be [=,!=,>,>=,<,<=,TO,EX-
our case, for full-text search1, we want to create one that is
ISTS,IN,NOT,AND,OR]
detailed and organized, allowing us to build a strong search
system around this structure. However, not one that is either • value is the value theOPERATORshould look for in the
attribute
too loose or too granular that it could turn our documents so
saturated that, in the end, we end up getting a lot of unrelated NOTE: On Meilisearch, filters work only on string and
results or even querying the data harder than in our existing numeric values.
operational database. Examples:
Now that you know that a defined structure is valuable for a To search only within the programming category, you can do
database, we will present you with a powerful feature on text it as such:
search: filters. category = programming
A filter gives you better control over the results, allowing
you to run more fine-grained queries rather than just To search within a group of categories:
returning all matches from the existing set of records.
This is done by extracting subsets of your data based on category IN [programming, database]
your structure… I hope you are seeing the benefits of struc-
Another way of writing this statement would be:
turing your data right now.
For this section, let’s use the following example document: category = programming OR category = database

{ To find books whose price are greater than a certain value:


"id": 15,
"title": price > 10
"The Complementary PHP Testing Tools Cookbook",
"description":
"Learn how a Grumpy Programmer approaches \ Grouping Filters
improving his own codebase, including all of \ Sometimes, you need to run queries with multiple filters,
the tools used and why.",
"category": "programming",
and the way of gluing them together is by using AND and OR
"price": 29.99, operators. But there is an important thing to keep in mind:
"number_of_pages": 46, Meilisearch reads expressions from left to right, so we might
"digital_formats": [ not get the results we expected, especially when dealing with
"PDF", OR statements.
"ePub",
"Mobi",
Example:
], The expression: price < 50 and category = 'database' OR
} title = 'programming'
will be evaluated as (price < 50 and category = 'data-
Diving into the code: base') OR title = 'programming'
For this article, we will be using Meilisearch2. Instead, you should group what needs to be evaluated
To use a filter, you must define it first, it can't be done together with parenthesis in order to get the results you want:
on runtime. price < 50 and (category = 'database' OR title = 'program-
Let’s say you want to create a filter for category, price, and ming')
digital formats; you would do it as follows: Bonus: Notice a ‘single thing’ different from the previous
examples aside from the grouping? If not, it is a simple one
$client->index('books')
->updateFilterableAttributes(
and here it goes, whenever you are using filters with strings
['category', 'price', 'digital_formats'] containing white space or reserved keyword such as opera-
); tors (IN, OR, AND, etc…), you need to encapsulate the string
with either single or double quotes, example:
1 https://phpa.me/full-text-search
2 https://www.meilisearch.com/docs

4 \ July 2024 \ www.phparch.com


Advanced Search For Laravel Applications

author = "John Doe" To override these default settings, you can use theminWord-
author = 'John Doe' SizeForTyposobject.
title = 'AND'
$client->index('books')->updateTypoTolerance([
Otherwise, the parser might not be able to handle your 'minWordSizeForTypos' => [
query and throw an error. 'oneTypo' => 3,
'twoTypos' => 8
]
Facets ]);
Ever wondered how online stores automatically organize
Keep in mind that oneTypo has to be between 0 and
categories based on your search parameters? This is done
twoTypos ( 0 <= oneTypo <= twoTypos ), and twoTypos have to
using an approach called Faceted Search.
be less than 255 (twoTypos <= 255).
A faceted search is a method of searching where users can
trim the results by filtering groups of data—each group is Disabling Typo Tolerance in Certain Cases:
called a Facet. It is an intuitive way of displaying data and By default, typo tolerance is applied to every token, but
allowing users to navigate through content. In Meilisearch, what if you don’t want to allow a typo for certain words? For
this is done through filtering, as facets are a specific use case example, we might not want results for MySQL to be mixed
of filters. with NoSQL and vice versa.
Let’s say you have an online clothing store—users looking For this scenario, we have the disableOnWords setting:
for “Blue t-shirts” will have a huge list of results. Having this
amount of results can be overwhelming, but, if we implement $client->index('books')->updateTypoTolerance([
facets, they will be able to reduce the output by selecting 'disableOnWords' => ['nosql', 'mysql']
]);
things like Brand, size, material, etc… making the process
faster, easier, and less time-consuming. Let’s say we want exact matches for all book titles and not
Example of a faceted search interface3: (See Figure 3) only certain words; we can set the disableOnAttributes for
this particular case:
Figure 3.
$client->index('books')->updateTypoTolerance([
'disableOnAttributes' => ['title']
]);

Relevancy
When using a full-text search engine, we might end up
getting a lot of unrelated documents, or, better said, results
are not being displayed in the order that makes sense.
For this particular case, Meilisearch has several features for
fine-tuning the relevancy of search results, one of them being
Typo Tolerance ranking rules, which is a way to sort search responses based
on a consecutive set of rules.
One advantage of full-text search is that it has a higher level
Meilisearch has a predefined set of rules for all indexes. For
of tolerance of typos; for example, it can show results for “Red
each search, the results are sorted using bucket sort. The order
t-shirts” when users type “Red t-shorts”.
in which rules are applied matters, as only the first ranking
On Meilisearch, this feature is enabled by default, but we
rule is applied to all documents, and the subsequent rules are
can modify or disable it. Let’s take a look at a few settings
only applied if needed to break the tier.
customizations:
The default rules for search are (in order as of the time of
Number of Typos PER Token writing this article):
The default rule for typo tolerance on Meilisearch is: 1. Words Documents with more token matches from the
It will accept one typo for query terms containing five search string are ranked higher.
or more characters and up to two typos if the term is at
least nine characters long. 2. Typos Documents with the less amount of typos are
Example: If you have a document that contains the word ranked higher.
mysql, searching for misql will match mysql because it has 5 or 3. Proximity Documents with the tokens being closer
more characters. On the other hand, searching for PGP will not together and in the same order of the search string are
match PHP as it has less than 5 characters. ranked higher.
4. Attribute This is based on the Attribute ranking
3 https://phpa.me/faceted-search

www.phparch.com \ July 2024 \ 5


Advanced Search For Laravel Applications

order, tokens that are on more relevant attributes are Model Configuration
ranked higher. (On meilisearch you can define how You can tune several things inside your Laravel model; one
relevant an attribute is for search results, for example, of the most important is the data that will be fed to the search
the title might be more relevant than the author or engine. If not provided, Laravel Scout will simply dump the
category). toArray() response into the engine. This can result in unnec-
5. Sort When the sort ranking rule is in a higher posi- essary information being stored. To have better control over
tion, sorting is exhaustive: results will be less relevant the date, you can define the information you want to share as
but follow the user-defined sorting order more closely. follows:
When the sort is in a lower position, sorting is rele-
vant: results will be very relevant but might not always public function toSearchableArray() {
return [
follow the order defined by the user. 'id' => $this->id,
6. Exactness The more similar the tokens of the search 'title' => $this->title,
string and the document are, the higher the docu- // ...
ment’s ranking. ];
}
Note: you can change the order of the rules and even create
your own rules Index Settings
As part of your deployment process, you should run the
How to Integrate a Laravel Project following command to have the latest index settings:
Laravel has a first-party library for full-text search called php artisan scout:sync-index-settings
Laravel Scout4. I always recommend sticking to official
Laravel packages, as they play well together and allow for
better migration in case you need to. According to their How Scout Works Under the Hood
description, “Laravel Scout provides a simple, driver-based
solution for adding full-text search to your Eloquent models.” Laravel Scout works by running your searches directly on
Laravel Scout has official support for Algolia, Meiliseach, the search engine instead of the database.
Typesense, and a database driver (MySQL and PostgreSQL). After it gets the results, it fetches the objects from the data-
Also, you can easily extend it by creating a custom driver for base based on the defined key and value configuration and
your search implementations. hydrates eloquent.
Basically, it runs the following query on your database:
Installation
SELECT * FROM books WHERE id IN (12, 33, 45, 67)
Add the Dependency:
This being said, there are a couple of important things to
composer require laravel/scout take into consideration:
• The search engine and your eloquent database are inde-
Publish Configuration: pendent of each other, so things like query scopes and
This is important because you can set the index settings on Eloquent queries won’t be supported. They will need to
the config without the need to interact with the client: be applied manually on Scout.
For a better understanding, please refer to the following
php artisan vendor:publish graph: (See Figure 4)
--provider="Laravel\Scout\ScoutServiceProvider"
Figure 4.
Set Up the Model
To make a model searchable with Laravel Scout, you need
to use the Searchable trait:
<?php

namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;
class Book extends Model {
use Searchable;
}

4 https://laravel.com/docs/11.x/scout

6 \ July 2024 \ www.phparch.com


Advanced Search For Laravel Applications

Content Management Cycle Book::search('Concurrency in PHP')


->whereIn('digital_formats', ['PDF', 'ePub'])
It is important to know that Laravel Scout does not automat- ->get();
ically feed the information to the search engine for existing
records on the database. To do so, you will need to call: Advanced filtering When you need to run more complex
php artisan scout:import "App\Models\Book" queries, search accepts a second attribute:
For existing records:
Laravel Scout will automatically sync your models when- Book::search('Concurrency in PHP',
function (
ever you call the save() method or create a new record. $meilisearch, string $query, array $options
Ex: ) {
$options['filter'] = '(price >= 10 AND ' .
'price <= 50) OR is_highlighted = 1';
use App\Models\Book; return $meilisearch->search($query, $options);
});
$book = new Book;

// book data
Complementing Your Queries with
$book->save();
Your Operational Database
We recommend using a queue, by setting 'queue' => true
After Scout has delivered all the results, you can make use of
on your config/scout.php file and setting up a queue on your
the query method to do further customization, this time, on
Laravel installation if you haven’t. This way, you can offload
your operational database side. Keep in mind that you should
this operation from the main request and process it in the
not use this for filtering; instead, use the where statements
background.
described in this article.
Note: Whether you set the queue to false or not, Meilisearch
will perform the process asynchronously, and changes might Book::search('Concurrency in PHP')
not reflect immediately. ->query(
Finally, to set meilisearch as your scout search engine: fn(Builder $query) => $query->with('reviews')
)->get();
First, install the Meilisearch PHP SDK package:
composer require meilisearch/meilisearch-php \
http-interop/http-factory-guzzle Final Words
Lastly, set the following .env variables: Full text search is a powerful tool that will allow you to
improve your search by allowing more complex queries with
SCOUT_DRIVER=meilisearch faster responses and less resource usage. After reading this
MEILISEARCH_HOST=http://127.0.0.1:7700
MEILISEARCH_KEY=masterKey
article, you already know the basics and can integrate it into
your project easily, no matter the engine; as long as it has
Laravel Scout support, you are good to go.
Not that you have set up everything, but you will be able to
run queries using eloquent; here are some examples: César is a Systems engineer with more than
Run simple searches: 8 years of working with many PHP tools
To run a search all you need to do is use the search method, such as Laravel, Phalcon, Yii, and WordPress.
which is defined in the Searchable trait. Fan of optimizing things, adventurer &
curious. When not programming you can
Book::search('Concurrency in PHP')->get(); find him doing road trips, hiking, cooking,
and creating new Dragon Fruit breeds.
Run searches with filters: Laravel Scout query builder has @CamvDev
3 simple implementations of the where clause: where, whereIn,
and whereNotIn. These clauses only support scalar values
such as integers and strings, limiting when you need to imple-
ment things like nested queries, less or greater operators, etc…
Let’s say you want to query books within a category; you
just need to run:
Book::search('Concurrency in PHP')
->where('category', 'programming')->get();

In the same way, if you need to match against many


elements, you can use whereIn:

www.phparch.com \ July 2024 \ 7


The essential tool for working
with dependencies in PHP
Private Packagist gives you fast, reliable, and secure access to
your internal and mirrored copies of third-party packages.

Reliable access Security monitoring

Copies of the open-source and other Receive alerts or weekly summaries when
third-party packages you use are stored, security vulnerabilities are reported for
so no need to worry about availability. Composer dependencies in your projects.

Review dependencies Quick setup

Automated comments on your pull Private Packagist integrates with GitHub,


requests allow you to quickly review any Bitbucket and GitLab and supports any
dependency changes. code hosting platform using git, svn or hg.

Use code ARCHITECT24


packagist.com for 10% off your first year.

Hundreds of companies trust Private Packagist.


Security Corner

PHP Under Attack


Eric Mann
There are many things that need to happen just right for PHP to be vulnerable to a buffer overflow
bug. Yet that won’t stop the sensationalized stories about PHP supposedly being insecure.
Ironically, this isn’t even a bug in PHP itself but in an upstream library that PHP (and other tools) use.

I attended college at the University of Oregon in Eugene.


While I was there, I worked for the Housing department and PHP and Vulnerabilities
did a lot of work with Residence Life. This included serving This past April, a security researcher discovered a buffer
on a panel of students who consulted on the design of the (at overflow bug in glibc1—the GNU C library used by PHP.
the time) brand-new Living Learning Center in the middle of Specifically, there is an issue in character set conversions from
campus—a building that merged dorm living with education any other encoding type to the ISO-2022-CN-EXT character
in some unique and interesting ways. set that could permit remote code execution in some appli-
We were consulted on everything from the layout of indi- cations.
vidual dorm rooms to the structure of the classrooms on the The descriptions of the bug in media portray it as a world-
main floor. I was there long enough to see the initial ground- ending scenario for PHP. It was so bad that the core team
breaking, the continued construction, and tour the building needed to publish a public statement2 on php.net3 explaining
before its grand opening. During that tour, a friend and I the bug and the narrowly specific way it could show up in an
discovered a minor design flaw in the building. application. Specifically, an application is only vulnerable to
this bug if:
Figure 1. • Upstream updates to glibc have not been installed
• The iconv extension is loaded
• The vulnerable character set (ISO-2022-CN-EXT) is avail-
able in gconv-modules-extra.conf
• The application passes a user-defined character set
to iconv
There are many things that need to happen just right for
PHP to be vulnerable to this bug. Yet that won’t stop the
sensationalized stories about PHP supposedly being inse-
cure. Ironically, this isn’t even a bug in PHP itself but in an
If you were really into rock climbing, you could scale the upstream library that PHP (and other tools) use.
wall around the elevator, using the decorative brick cutouts
as handholds. We thought this was entertaining and demon- More Edge Conditions
strated it immediately to the staff giving the tour. More recently, just in June, PHP patched CVE-2024-45774
The Residence Life Director was horrified. The little brass in versions 8.1 and above. Once again, this is a critical issue as
inlays that are now present in the wall were installed the it potentially allows an attacker the ability to execute arbitrary
following week. code on an impacted server. As with any such issue, it needs
Was this really a risk? Yes and no. A lot depends on what to be addressed and repaired urgently.
you’re trying to protect against and how. The reality was it But again, the bug is only present in systems running on
would take a lot of physical stamina and grip strength to scale Windows using PHP in CGI mode. It’s still a critical issue that
that wall in the first place. Once you’d managed to do so, you must be fixed, but impacts a small subset of applications in a
would still need to somehow climb over a 4-foot tall smooth very specific niche of the market.
wall to make it onto the roof. Further, any door from the roof If you’re not on Windows, this bug doesn’t impact your
leading inside the building was kept locked, and an alarm was application. You’re not impacted if you are on Windows but
triggered if opened. using IIS by default with PHP in FastCGI mode. If you’re
Still, the concern remained, so the design flaw was corrected,
even though it wasn’t really a risk in the first place. 1 https://phpa.me/cve-2024-2961
2 https://phpa.me/php2024-04-24-1
3 http://php.net
4 https://phpa.me/cve-2024-4577

www.phparch.com \ July 2024 \ 9


Security Corner
PHP Under Attack

on Windows using PHP in CGI mode, update to the latest


Related Reading
release of your chosen flavor of PHP to receive the patch.
• Security Corner: Security Minded Code Review by Eric
Is the Sky Falling? Mann, June 2024.
http://phpa.me/2024-06-security-corner
My entire career has been filled with other developers
• Security Corner: Rolling Credentials and Keys by Eric
turning up their noses at my choice of programming language.
Mann, May 2024.
Retorts of “that’s a horrible language” or “you must not care
http://phpa.me/2024-05-security-corner
about security” are frequent comments made at events when
I introduce myself and talk about PHP. • Security Corner: Security and Side Channels by Eric
Hearing about these critical CVEs makes it easy to believe Mann, April 2024.
that the haters might be right. But rest assured—they are not. http://phpa.me/2024-04-security-corner
PHP today is used on more than 75% of all websites5. It
comes standard on most servers. We present a truly massive
attack surface for anyone to find fault with. This doesn’t mean
that PHP is inherently insecure. Quite the opposite—due to Eric is a seasoned web developer experi-
the massive scale of PHP utilization and the attention the enced with multiple languages and platforms.
language receives, it is arguably more secure than many alter- He’s been working with PHP for more than
natives that don’t benefit from this level of adversarial analysis. a decade and focuses his time on helping
The sky isn’t falling. PHP is and will continue to be a strong developers get started and learn new skills
with their tech of choice. You can reach out
and secure choice for building applications. But it will also
to him directly via Twitter: @EricMann
continue to see attacks and analysis that result in strength-
ening the stack—make sure you’re keeping your own system
up to date to benefit from the improvements!

5 https://phpa.me/php-usage-0321

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

10 \ July 2024 \ www.phparch.com


2500 Feet

User Workflow Improvement with Hedy


Lamarr
Edward Barnard
The programming language, Hedy, is named after actress Hedy Lamarr. I will first explain Lamarr’s
accomplishments in an attempt to find the connection. Next, I’ll examine Hedy, which addresses
the need for more computer programming literacy. Finally, I have suggestions for more reading
about programmers’ brains.

Sometimes, I prefer to delay writing Now, in 2024, Hedy Lamarr is better among the richest in the world. He
an essay for php[architect] when the known as The Mother of WiFi2 than as made his fortune manufacturing and
code or feature isn’t actually working Delilah in the 1949 film Samson and selling arms and ammunition to both
as of yet. Right now is one of those Delilah3. What else was remarkable sides of numerous conflicts.7
times. Emergencies happen. It’s quite besides how she used a player piano
fortunate that I got lucky. A thoughtful roll to describe a frequency-hopping As Madame Mandl, I presided at
php[architect] reader, Karina, baited a algorithm that was, decades later, re-in- parties honoring all kinds of nota-
hook for me and tossed it into the open: vented for WiFi, Bluetooth, and CDMA bles, from stage and screen stars to
cell phone transmissions? Hedy Lamarr heads of state. There were fine men,
Today I learned that Hedy (a co-invented a “secret communication and there were bounders. Posturing
localizable textual programming system” with composer George Antheil. Adolf Hitler kissed my hand, and
language for the classroom) is That invention describes frequen- on another occasion pompous little
named after Hedy Lamarr. cy-hopping transmissions now used Mussolini held my chair. Enter-
for WiFi, Bluetooth, GPS, etc. Antheil’s taining was Mandl’s way of doing
younger brother Henry Antheil Jr. was business.
I asked myself, “Hedy as a program-
ming language?” I didn’t see the an American diplomat shot down
connection. In keeping with this by Soviet bomber aircraft on June 14, Hedy, as showpiece Madame Mandl,
column’s theme, let’s first look for a 1940. That downed aircraft was recently was literally a prisoner. After various
1940s connection, perhaps to mili- discovered, more than 80 years later failed escape attempts, she laid plans
tary aviation. When first looking at a https://phpa.me/finland-missing-plane4. as she interviewed women as a replace-
problem, it’s often necessary to do a bit We know that during that time period ment maid. One, Laura, was as tall as
of digging to turn up useful details–or (the 1940s), actress Ann Sheridan5 she was and with similar coloring and
to define the boundaries of the problem. was the second-most-favorite actress looks. Hedy practiced Laura’s walk and
No less a personage than William F. among the soldiers. Hedy (rhymes with talk.
Friedman, founding cryptanalyst of the “lady”) was first. That doesn’t sound like
National Security Agency, told us this is it leads to a programming language. It had been his game to keep me a
the case:1 Hedy Lamarr was born in Vienna, prisoner. It had been my game to
within the Austria-Hungarian Empire, escape. He had lost.8
Often, indeed, the student will not on November 9, 1914, though her
even know whether he is on the autobiography says 1915.6 She was
Have we learned anything relevant to
right track until he has performed a the teenage bride of Fritz Mandl, an
the problem at hand? Yes, perhaps we
large amount of preliminary “spade extremely dangerous man who was
have. One goal was to define the bound-
work” involving many hours of
aries of the problem. Hedy Mandl was
labor. 2 https://phpa.me/mother-of-wifi able to escape in that most dangerous
3 https://www.imdb.com/title/tt0041838/ of games.
1 William F. Friedman. Military
4 https://phpa.me/finland-missing-plane
Cryptanalysis, Part I, Monoalphabetic
Substitution System; 4th Edition, Fried- 5
man, Revised And Enlarged By Callimahos. https://en.wikipedia.org/wiki/Ann_Sheridan
Accessed June 9, 2024. http://archive.org/ 6 Lamarr, Hedy. Ecstasy and Me: My Life
details/41748389078762, Section I page 2, pdf as a Woman. New York: Bartholomew House, 7 Lamarr Ecstasy, page 21.
page 32. 1966, page 16. 8 Lamarr, Ecstasy, page 34.

www.phparch.com \ July 2024 \ 11


2500 Feet
User Workflow Improvement with Hedy Lamarr

Hedy Lamarr, in her autobiography, does not mention Programming syntax errors, by their opaqueness, present
any of her inventions. That, obviously, was not what people early barriers to success. I immediately flashed back to
wanted to hear.9 that worst and most fearsome horror from my distant past:
IEY013I SYNTAX spewed within my IBM System/360 Fortran
The first important actresses I met were Joan Crawford IV (G Extended) program listings. Figure 1 (next page) shows
and Joan Bennett. It was at the Turf Club of Santa Anita a program listing with errors. Figure 2 (next page) shows the
racetrack. (Joan Crawford was to be named actress who opaque explanation. Figure 3 (next page) shows how IBM
acted most like a Hollywood star. Joan Bennett had the thought of software developers in the early 1970s.12
greatest dignity. She was from a highly regarded theatrical Note that IBM specifically explains: “If a statement cannot
family. And I was often to be called Hollywood’s most be identified, this error message is used.” This horror-in-
beautiful star.) ducing message appears when you have made a mistake and
likely have no idea what is wrong, and neither does IBM’s
One further connection should not be a surprise: Several Fortran compiler. It’s no wonder so many would-be student
World War II aircraft were named after her–or, specifically, programmers make the War Games computer’s choice, “The
named “Tondelayo”10 after her role in a 1942 movie. Alberto only winning move is not to play”, and choose a different area
Vargas created publicity (pin-up) art with Hedy Lamarr as of study.
Tondelayo. One airworthy B-25J Mitchell currently flies as
Tondelayo. User Workflow Improvement
Professor Hermans’s paper is well worth reading, and
Reset here’s why. Through user experience research (students
We’ve now gathered some facts, most of them scandalous. writing Hedy code), she makes it easier and easier to learn
Let’s see what we can find out about the programming the programming language. She created levels, each level
language Hedy. Professor Felienne Hermans, the language being more advanced until the final level is programming in
creator, presented an ACM paper.11 Python. That’s not the point I’m making.
Is this a dead end as well? Hedy is a programming language Walk through her paper as she describes her thought
aimed at children aged 11-12. On the contrary, Hermans process for improving Hedy based on programming-error
explains: statistics. She’s smoothing out the students’ workflows and
improving the student experience based on the classes of
With computer technology being a tool of rapidly growing errors they are making.
importance in nearly all aspects of life, the world also My users, in particular, are famous for not following the
needs programmers. In fact, the world needs ever more webpage workflow I designed. Users find multiple ways to
programmers; the number of software developer jobs is surprise me, sometimes at the rate of hundreds of surprises
expected to grow with 17% by 2024, much faster than the per second. This approach is different from a sales funnel
average rate among other professions. aimed at getting the user to the end goal (finalize a sale).
Hermans’s approach aims to make unfamiliar tasks easier
to perform. In her case, it’s about writing code as you’re just
We’re in the industry; we all know this to be true. However,
learning to code, but I see this as a valuable way of improving
Hermans, with Hedy, is on to something. She explains that
any user workflow.
young learners often struggle with the syntax of a program-
ming language in addition to learning what computer
programming is all about, what an algorithm is, and so on. The Programmer’s Brain
She notes that when very-young students are first learning Professor Hermans wrote a book a few years ago; it’s on
to write, they write lower-case words without punctuation. my shelf, The Programmer’s Brain: What Every Programmer
Studies have shown, she says, that correctly placing punctua- Needs to Know About Cognition.13 I never finished the book
tion is cognitively more difficult and more advanced. because everything seemed obvious. It’s time I took another
look at the book! Also, on the Manning website, there’s a free
9 Lamarr, Ecstasy, page 56. excerpt, “The Act of Writing Code.”14
10 PacificWrecks.com. “Pacific Wrecks - B-25D-20 ‘Tondelayo / Chow
Hound’ Serial Number 41-30669.” Accessed June 9, 2024. https://phpa.
me/pacificwrecks. 12 Ibm:: 360:: Fortran:: GY28-6638-2 OS360 FORTRAN IV G Com-
piler Program Logic Manual 1972-12. https://phpa.me/bitsavers.
11 Hermans, on the first page, grants permission to quote from this
paper. Hermans, Felienne. “Hedy: A Gradual Language for Program- 13 Hermans, Felienne, and Jon Skeet. The Programmer’s Brain: What
ming Education.” In Proceedings of the 2020 ACM Conference on Every Programmer Needs to Know about Cognition. Shelter Island:
International Computing Education Research, 259–70. ICER ’20. New Manning, 2021.
York, NY, USA: Association for Computing Machinery, 2020. https:// 14 “The Act of Writing Code.” Manning (blog), January 26, 2022.
doi.org/10.1145/3372782.3406262. https://phpa.me/act-of-writing-code.

12 \ July 2024 \ www.phparch.com


2500 Feet
User Workflow Improvement with Hedy Lamarr

Figure 1. Solving the Question


Did we actually figure out the connection between actress
Hedy Lamarr and the Hedy programming language? We did
not. The correct approach would have been to ask the author.
Sometimes, the simple ways are best. Interviewer Ana Mogul
asked, “Where did the name for Hedy come from?”15

Hedy is named after Hedy Lamarr! She is most known as


a movie actress, but she was also an inventor, who helped
develop the “frequency hopping” algorithm that you use
everyday, since it is in Wi-Fi routers!

However, that’s not the full answer. It’s clear from that
interview that presenting a woman as a role model is crucial
in this context. Hermans continued:

Kids in my classes that were struggling with error messag-


Figure 2.
es, especially the girls who would come in a bit nervous
already into programming class. If the first thing you then
see is “SyntaxError, unexpected EOL” it is easy to think
you are not smart enough to be good at computers.

I do think Felienne and Hedy are on to something. I


certainly appreciate that hint tossed my direction! But I also
recommend that original ACM paper as directly useful to UI
and user workflow design.

Additional Reading
I highly recommend Hedy’s Folly: The life and breakthrough
inventions of Hedy Lamarr, the most beautiful woman in the
world by Richard Rhodes. It’s well-researched, and Rhodes
Figure 3. has a great way of connecting different threads into an
understandable picture. Lamarr’s autobiography is quite
enlightening about how things were in the 1930s and 1940s,
particularly as affected by the Hollywood glamor machine.

Ed Barnard had a front-row seat when


the Morris Worm took down the Internet,
November 1988. He was teaching CRAY-1
supercomputer operating system internals to
analysts as they were being directly hit by the
Worm. It was a busy week! Ed continues to
indulge his interests in computer security and
teaching software concepts to others.

15 Mogul, Ana. “Console #186 - Interview with Felienne of Hedy - a


Language for Children to Learn Programming.” Substack newsletter.
Console by CodeSee.Io (blog), December 3, 2023. https://console.sub-
stack.com/p/console-186.

www.phparch.com \ July 2024 \ 13


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]
Education Station

Backward Compatibility
Chris Tankersly
“WE DO NOT BREAK USERSPACE!”—Linus Torvalds, https://lkml.org

PHP should be Linus’s favorite language, as the internals


group routinely (and to many, incorrectly) adheres to the Not Breaking Things
idea of backward compatibility. There are plenty of things that
existed in the language for far too long, like register_globals “If a change results in user programs breaking, it’s a bug
or magic_quotes1, existing until PHP 5.4. Now that the inter- in the kernel. We never EVER blame the user programs.
nals group releases major versions more regularly and has How hard can this be to understand?”—Linus Torvalds,
more opportunities to remove code, there is still a general lkml.org2
idea of not breaking backward compatibility.
Why is there such an effort to prevent user code from One of the best ways to preserve backward compatibility is
breaking? Breaking a user’s code out of nowhere leads to to ensure that the changes you make do not affect the devel-
frustration, and in many cases, users move on to something oper implementing your code. There are two ways to help
else. If you program in PHP, you can usually expect that code ensure your changes are not breaking compatibility.
to work across multiple versions without any change. There is
a good chance that unless you were doing something weird, Unit Tests
vanilla code from PHP 5.3 will run fine on PHP 8 without If you already have unit tests, great! If you make a change
any issues. to your code without having to modify your unit tests, you
Many scripts exist that no longer have maintainers but still know you have preserved backward compatibility. This is the
function just fine. Should a user doing a simple update of best kind of refactor for users; as you know, your changes do
PHP expect their code to just break? What happens if that not affect any code that calls yours.
script was a critical business function? Is internals in their If you are writing an application or something that a user
right to just break code because a change wanted to be made interacts with over HTTP, like an API, then integration tests
in the engine? will be key. You want to make sure that all your existing tests
In a perfect world, users would read release notes, watch work just fine and that any new tests cover just added features.
deprecation notices, and clean up code for the easiest upgrade The key is that your existing tests work, so an outside user
experience possible. In the real world, however, users run an should also not break.
apt update or brew update and deal with the consequences What happens if you do not have any unit tests? At the very
after the fact. If your language broke your code every upgrade, least, start to write simple unit tests for the code you want to
you would never update. refactor before refactoring. While there can be a debate over
We went through this. It took a long time to move people to Test Driven Development versus Test Last Development3
PHP 5.3 from PHP 5.2 because it was perceived as a massive when it comes to new code, if you have existing code, you
backward compatibility break. Operating Systems would not will want to write your tests before refactoring if you intend
update to PHP 5.3 mid-lifecycle, so users never moved. At to maintain backward compatibility. You need to know where
my day job, we still have to support Java 8 because customers you are now to make sure you do not deviate.
will not update. The point of this article is not to introduce you to Test-
The flip side of this is the work I do in node.js—it feels like Driven Development or unit testing itself, so I would
every few weeks, I am dealing with some sort of backward recommend taking a look at PHP: The Right Way’s testing
compatibility break in one thing or another. This causes a section4 for information on testing.
massive amount of additional unplanned work. If a project
sits too long without maintenance, it becomes a full week of
Following Semantic Versioning
sorting out changes. Whether or not you have a suite of unit tests, simply
Most of this is all at the language level, though, but what following Semantic Versioning5 (or SemVer) can help you
about the library or application level? decide if a change is breaking or not. Semantic Versioning is
I think we would all do better by following Wordpress’s
example of encouraging backward compatibility when
possible while actively helping users upgrade. How do we do 2 https://lkml.org/lkml/2012/12/23/75
that? 3 https://phpa.me/tdd-benefits
4 https://phptherightway.com/#testing
1 https://www.php.net/releases/5_4_0.php 5 https://semver.org

www.phparch.com \ July 2024 \ 15


Education Station
Backward Compatibility

a standard for determining how you increment the version declare(strict_types=1);


number of your application or library.
function getUserData(int $id): array {
From semver.org6: global $guzzleClient;
return json_decode(
Given a version number MAJOR.MINOR.PATCH, incre- $guzzleClient->request('GET', "/user/{$id}")
ment the: ->getBody(),
true
);
MAJOR version when you make incompatible API chang- }
es
In most cases, this should be safe. strict_types is mostly
1. MINOR version when you add functionality in a dependent on the calling script to fully enable strict typing on
backward-compatible manner anything in pulls in via require or include. Nothing will break
until the calling code from the user adds declare(strict_
2. PATCH version when you make backward compat- types=1); to their own code. If all we do is add strict typing,
ible bug fixes Additional labels for pre-release and this is a MINOR change. We added functionality without
build metadata are available as extensions to the breaking the user’s code.
MAJOR.MINOR.PATCH format. Where we get into trouble is changing the signature of the
method call to something else. In the above code, we should
When you make a code change or do a code review, think not be using the global keyword to pull in an object; we really
about whether the change will cause you to do a MAJOR should be returning a proper value object. If we change the
version bump. If it does, then it may be worth refactoring to signature to force the user to pass in an HTTP client and we
see if the feature can be implemented in such a way as not to change the return signature (even if it was not declared with
break backward compatibility. type hinting), we will make a breaking change:
It is recommended to follow SemVer because dependency
resolution and compatibility are baked into Composer7, the declare(strict_types=1);
most widely used PHP package manager. Composer will not use GuzzleHttp\Client;
use App\MyAPIResponse;
allow you to install packages that are outside of the range
specified in your application. It also makes sure to only allow function getUserData(
upgrades within the same MAJOR version of a package int $id, Client $guzzleClient
and requires manual intervention when changing MAJOR ): MyAPIResponse {
version requirements. It does a very good job of making sure $data = json_decode(
$guzzleClient->request('GET', "/user/{$id}")
you cannot install code that might break your application. ->getBody(),
If a library does not follow SemVer, you lose a lot of these true
protections. );
What are some common changes, and where do they sit in return new MyAPIResponse($data);
regards to MAJOR, MINOR, or PATCH? }

Method/Function Signature Changes The user will have to change their code to pass in the
The most common way to break backward compatibility is Guzzle client and will have to know how to work with the
to change the incoming signature of a method or function or new MyAPIResponse object. The user must make a change to
change its output. Consider the following example: their code, so a MAJOR change has been made.

function getUserData($id) { Dependency Updates


global $guzzleClient; While it may seem somewhat counter-intuitive, updating
return json_decode( dependencies by themselves does not cause a MAJOR version
$guzzleClient->request('GET', "/user/{$id}") change. When someone includes your packages, it is accepted
->getBody(),
true that they are pulling in your package to use the code you are
); providing. They are not pulling in your library to directly use
} some other dependency.
For example, my pocket-framework/framework package
If we decide to tighten up this method with something as has a dependency on laminas/laminas-diactoros. That is an
simple as introducing static typing, what are the repercus- internal dependency for my package, and I do not expect
sions for the end user? a user to use classes from that library in their own code. If
a user wants to use Diactoros, they should add an explicit
dependency for it in their application, not rely on my depen-
6 http://semver.org dency on it.
7 https://getcomposer.org

16 \ July 2024 \ www.phparch.com


Education Station
Backward Compatibility

If I decide to switch to something else, like implement $guzzleClient = new Guzzle\Client(


PSR-78 and drop Diactoros, that is an internal implementa- ['base_uri' => '<https://example.com>']
);
tion detail. I do not expect a user’s code to break from this
change—the signatures that I have in my code for my classes // Traditional invocation with parameter order
and functions have not changed. $userData = getUserData(1234, $guzzleClient);
Changing a dependency is a MINOR change. // Named arguments
Along those same lines, changing the version of a depen- $userData = getUserData(
id: 1234,
dency of a package or the PHP runtime itself is generally guzzleClient: $guzzleClient
considered a MINOR change. Composer will handle finding );
a compatible set of packages for the user. This might lock the // Named arguments can be any order
user out from getting the latest version of a package, but that $userData = getUserData(
is fine. The user’s code will not stop functioning in its current guzzleClient: $guzzleClient,
id: 1234
state, as Composer will not allow that to happen. );
Long-Lived Bugs
While this is a great addition to the language, it does
There is an old joke that bugs are not bugs, just unintended introduce a new layer of backward compatibility—param-
features. Most of the time, this is used ironically for dark eter naming. Before this, you could rename a parameter on
usage patterns, but what happens when a sizeable audience a method or function without worrying about breaking an
depends on a bug? implementation’s code. The calling code had no idea what the
I took over a package years ago that handles deciding if a parameter names were; it just needed to make sure to pass
cron expression needs to run right now. This package still values in the correct order.
underpins a major framework’s scheduling system. After With Named Arguments, users who elect to use that feature
running for years, it pointed out that a commonly used will break if you change the name of a parameter. Changing
expression for running things quarterly was wrong9. a parameter name to make it clearer is now considered a
The bug only allowed quarterly crons to run on March, breaking change even if you do not use Named Arguments in
June, September, and December. The actual spec should have your own code. For example, if we change the $id parameter
only allowed runs on January, April, July, and October. This to $userId to be more explicit, it will break anyone calling the
was a common bug across many implementations; however, old name of the parameter:
digging into the source code of the original inspiration,
cronie10 revealed the bug. function getUserData(
This posed a problem—what about people who depend int $userId, Client $guzzleClient
on this bug? If I fix it, I’ve potentially broken an unknown ): MyAPIResponse {
$data = json_decode(
number of users that did things quarterly, like run reports. If $guzzleClient->request('GET', "/user/{$userId}")
they expect the code to execute in March, and now it runs in ->getBody(),
April, that can throw all sorts of wrenches into innumerable true
gears. );
In the end, I decided to respect backward compatibility. return new MyAPIResponse($data);
}
What should have been a PATCH to fix a bug turned into a
MAJOR change due to the reliance on the buggy behavior. It // This is now broken
was better to release a major version than break user code. $userData = getUserData(
If this had been a security issue rather than a usage issue, id: 1234, guzzleClient: $guzzleClient
I probably would have gone ahead with the PATCH update. );
The longer a bug exists, though, the more likely it is to have
become a feature.
Providing an Upgrade Path
Parameter Name Fixes
At some point, you will need to update your code in a way
Ever since PHP 8.0, you can use a feature called Named that will break backward compatibility. When we do, we
Arguments11. This allows a developer to specify parameters should strive to signpost to a developer where there will be
in any order by passing the name of the parameter along with changes and write code to help them get ready for eventual
the value. The syntax is paramName: value when passing the upgrades.
value.
Deprecation Notices
8 https://www.php-fig.org/psr/psr-7 Deprecation notices are a great way to alert a developer
9 https://phpa.me/laravel-issue-19532 that something will change in the future. To deprecate some-
10 https://github.com/cronie-crond/cronie thing means you are slating it for removal in the future, not
11 https://phpa.me/php-named-args removing it right now. This allows you to start to lock down

www.phparch.com \ July 2024 \ 17


Education Station
Backward Compatibility

incoming parameters without adding strict typing at the


Listing 2.
moment and is a built-in way of alerting a developer that a
change is coming. 1. use GuzzleHttp\Client;
This is done by calling the trigger_error() 12 function in 2.
PHP. You pass it a message and an alert level13 like E_USER_ 3. function getUserData(int $id): array {
DEPRECATED and PHP will bubble it up to the user depending 4. global $guzzleClient;
on their warning level. In most cases this will probably be 5. return json_decode(
6. $guzzleClient->request('GET', "/user/{$id}")
done to a log file or the command line if they are running a
7. ->getBody(),
development server. 8. true
If we take our function from earlier, we can add some addi- 9. );
tional checking. We know that we want to make sure that $id 10. }
is an integer and that we should move away from using the
global $guzzleClient to a passed-in argument. Let’s start by
adding some alerting around $id. (See Listing 1) Listing 3.
Listing 1. 1. use GuzzleHttp\Client;
2.
1. function getUserData($id): array { 3. function getUserData(
2. if (!is_int($id)) { 4. $id, Client $client = null
3. trigger_error( 5. ): array {
4. 'Passing a non-integer ID is deprecated ' . 6. if (!$client) {
5. 'and will changed in the next major version', 7. trigger_error(
6. E_USER_DEPRECATED 8. __FUNCTION__ . ' will require passing a ' .
7. ); 9. 'GuzzleHttp\\Client in the future',
8. 10. E_USER_DEPRECATED
9. $id = (int) $id; 11. );
10. } 12.
11. 13. global $guzzleClient;
12. global $guzzleClient; 14. $client = $guzzleClient;
13. return json_decode( 15. }
14. $guzzleClient->request('GET', "/user/{$id}") 16.
15. ->getBody(), 17. return json_decode(
16. true 18. $client->request('GET', "/user/{$id}")
17. ); 19. ->getBody(),
18. } 20. true
21. );
22. }
Why do we do this instead of just type-hinting int $id? I
prefer leaving off the type hinting so that the user knows we
want them to stop passing a non-integer. If we add the type Listing 4.
hinting right now, the $id value will get coerced to an integer.
This will work until the user changes their code to strict 1. use GuzzleHttp\Client;
typing, at which point their code will break. I would rather 2.
alert them to start passing an integer now than set them up 3. function getUserData($id, Client $client): array {
4. return json_decode(
for a breakage later on.
5. $client->request('GET', "/user/{$id}")
We do not remove the deprecation notice or finalize the 6. ->getBody(),
change until the next major release. At that point, we can pull 7. true
out the trigger_error() call and add in the type-hint safely: 8. );
(See Listing 2) 9. }
How do we handle the change for passing in the Guzzle
client? The easiest thing to do is add an optional parameter at second parameter in the future. In the next release, we can
the end and default it to null. We can then call trigger_error() enforce this more cleanly: (See Listing 4)
when the user does not pass anything in: (See Listing 3)
This allows current users to still pass in just an $id param- New Encapsulation
eter but alerts them that the function will eventually take a The previous examples work well when you are not modi-
fying the overall structure much, but what happens when you
need to seriously overhaul some code? I will be the first to
12 https://phpa.me/trigger-error admit that the method signature for getUserData() is a bit odd.
13 https://phpa.me/errrofunc

18 \ July 2024 \ www.phparch.com


Education Station
Backward Compatibility

I would prefer that the client is passed in first, and things like Breaking Backward Compatibility
the $id or other restrictions passed in after that.
You will either want to migrate the old code to the new Lowers Adoption
structure, let’s say a ThirdPartyApi class, and have the old I have quoted him a few times during this article, but I
method call the new wrapper or have the new code call the would highly recommend watching Linus Torvald’s Q&A
old code. With the above code, we could structure our new session from DebConf 2014, specifically his answer on “Why
class to work with the old signature. (See Listing 5) desktop Linux sucks”14. While he is talking about the Linux
We call the new code from the old getUserData() method kernel, much of his rant is valuable to developers at all levels.
WordPress thrives in a world where they stick to a strict
Listing 5.
adherence to backward compatibility. Many developers,
1. use GuzzleHttp\Client; myself included, love to make fun of them for it, but at the
2. end of the day, developers use WordPress not because of the
3. class ThirdPartyApi { good things it does but because people know that the code
4. public function __construct( they write today will last for a long time. That is valuable to
5. private Client $client client shops and contract developers.
6. ){}
7.
Frameworks like Symfony do a great job of providing
8. public function getUserData(int $id) { forward-compatible code for new releases. It is faster moving
9. return json_decode( than WordPress, but Symfony generally does a good job
10. $this->client->request('GET', "/user/{$id}") alerting developers to deprecations, how to handle them, and,
11. ->getBody(), most importantly, leaving deprecations around until a major
12. true release happens.
13. ); I think we could all learn a lot from this idea:
14. }
15. } “We have one rule in the kernel, there is one rule: ‘We don’t
16.
break user-space.’ Everything else is kind of a guideline.”—
17. /**
Linus Torvalds, Debconf 2014
18. * @deprecated
19. */
20. function getUserData(int $id, Client $client): array { Maybe more libraries should take this to heart and make it
21. trigger_error( their number one rule.
22. __FUNCTION__ . ' is deprecated, use ' .
23. 'ThirdPartyApi::getUserData() instead',
24. E_USER_DEPRECATED Chris Tankersley is a husband, father, author,
25. ); speaker, podcast host, and PHP developer.
26. Chris has worked with many different frame-
27. return (new ThirdPartyApi($client)) works and languages throughout his twelve
28. ->getUserData($id); years of programming but spends most of
29. } his day working in PHP and Python. He
is the author of Docker for Developers and
works with companies and developers for
and start to move people to the new code, just like we did integrating containers into their workflows.
when we tightened up the code before. Since we are depre-
cating the entire method, I also added a @deprecated block
so that IDEs will notice the deprecation when doing code
completion. Editors like PHPStorm and VSCode will strike
through the function name and show the deprecation to the
developer while they edit code.
14 https://youtu.be/Pzl1B7nB9Kc?t=172

www.phparch.com \ July 2024 \ 19


Level Up

Understanding and Implementing a


Dependency Injection Container - Part II
Wendell Adriel
Last month, we started our magical journey into the Dependency Injection land, where we
learned important concepts about it and how important it is for modern PHP applications.

for exceptions that the PSR-11 provides to us. Inside the


Introduction Exceptions folder, create a file named NotFoundException.php.
This month, we are going to finish our journey by completing (See Listing 2)
the implementation of our DI Container. We will learn the
magic behind a DI Container and how we can implement it Listing 2.
to automatically resolve and build the dependencies needed
1. <?php
for our classes. 2.
By the end of the article, we will have a fully functional 3. declare(strict_types=1);
DI Container built from scratch that’s ready to be used in 4.
simpler applications. 5. namespace SimpleContainer\Container\Exceptions;
6.

Implementing the Get Method


7. use Exception;
8. use Psr\Container\NotFoundExceptionInterface;
9.
The first scenario we should check in our get method is if
10. final class NotFoundException extends Exception
the requested identifier is for a Singleton instance we already
11. implements NotFoundExceptionInterface
have instantiated. (See Listing 1) 12. {
13. public function __construct(string $message)
Listing 1. 14. {
15. parent::__construct($message);
1. public function get(string $id): mixed
16. }
2. {
17. }
3. if ($this->hasInstance($id)) {
4. return $this->instances[$id];
5. }
6. } Now that we have our exception implemented, we can
7.
use it for the scenario we described above. Update our get
8. private function hasInstance(string $id): bool
9. { method to add the following.
10. return array_key_exists($id, $this->instances); if (! $this->has($id)) {
11. } if (! class_exists($id)) {
throw new NotFoundException(
"'{$id}' is not a class name and " .
"is not set in the container"
As you can see, we have a really simple implementation in );
which we check the instances dataset for the identifier. If }
we find it, we return it, since a Singleton instance is already }
instantiated.
We have already tackled two scenarios; it’s time for the
third one. For this one, we are going to implement a solution
Identifiers Without Definition when we don’t have the definition for the identifier, but the
Now, we are going to check for the second scenario: If we identifier is a valid class, unlike the second scenario. For this
don’t have the definition for the required identifier and the one, we need to do two things:
identifier is not a valid class, it means that it is not a defined 1. Register the identifier in our DI Container.
class in our codebase. For this scenario, we will need to throw 2. Build an instance of the identifier to return.
an exception, and we need to implement one of the interfaces

www.phparch.com \ July 2024 \ 21


Level Up
Understanding and Implementing a Dependency Injection Container - Part II

Now, let’s add this block to our get method: Identifiers Already Registered
$this->set(id: $id); Now, we are going to tackle three more scenarios in which
we already have the identifier registered in our DI Container.
return $this->build($id);
For the fourth scenario, we are going to implement the
And we will also create some helper methods that we need. solution when the registered identifier definition is pointing
to a valid class by adding the following to our GET method.
public function set(
string $id, $definition = $this->definitions[$id];
mixed $value = null, if (
bool $singleton = false is_string($definition->concrete) &&
): void { class_exists($definition->concrete)
$this->remove($id); ) {
if (is_null($value)) { $instance = $this->build($definition->concrete);
$value = $id; if ($definition->singleton) {
} $this->addInstance(id: $id, value: $instance);
}
$this->definitions[$id] =
new ContainerItemDefinition( return $instance;
concrete: $value, singleton: $singleton }
);
} We also need to include our addInstance method:

public function remove(string $id): void private function addInstance(


{ string $id, mixed $value
if ($this->hasInstance($id)) { ): void {
unset($this->instances[$id]); $this->instances[$id] = $value;
} }
}
As you can see, we need to do three things to tackle the
private function build(string $id): mixed fourth scenario:
{
// TODO 1. Build the instance using the identifier definition.
} 2. Check if the definition is flagged as being a Singleton
instance, and if so, we need to add the built instance
As you can see, there are already some more things going
to our instances dataset.
on, so let’s understand everything. To register the identifier
in our DI Container and also to allow us to register items 3. Return the built instance.
directly, we created a set method. In this method, we do three Now, for the fifth scenario, we are going to implement the
things. solution when the registered identifier definition is a Closure.
1. We first call the remove method, which just checks if It’s going to be very similar to the fourth scenario. The only
we have a Singleton instance for that identifier and difference is that instead of building an instance with our
removes it because if we’re updating the identifier, build method, we are going to call the Closure passing our DI
we don’t want an outdated Singleton instance to be Container as a parameter to it.
returned.
if ($definition->concrete instanceof Closure) {
2. We check if the $value parameter is null; if it is, we $result = call_user_func(
use the identifier as the value. This is useful for cases $definition->concrete, $this
like the one we’re using in the get, where the defini- );
tion for the identifier is already the class that should if ($definition->singleton) {
$this->addInstance(id: $id, value: $result);
be built. }
3. We register the identifier in the DI Container along-
side its definition. return $result;
}
After registering the identifier in our container, we need to
build an instance of it and return it. We do this by calling our Last but not least, we have our sixth and final scenario:
build method, and then we finish our third scenario. We are if the registered identifier definition is neither a valid class
going to cover the implementation of this method later on nor a Closure, it can be anything else, like a normal string,
in the article since this is the backbone of our DI Container. an array, etc. In these cases, we just need to return the value
that’s defined. So, adding the final scenario, we have our get
method fully implemented as showing in Listing 3.

22 \ July 2024 \ www.phparch.com


Level Up
Understanding and Implementing a Dependency Injection Container - Part II

Listing 3. Listing 4.
1. public function get(string $id): mixed 1. <?php
2. { 2.
3. if ($this->hasInstance($id)) { 3. declare(strict_types=1);
4. return $this->instances[$id]; 4.
5. } 5. namespace SimpleContainer\Container\Exceptions;
6. 6.
7. if (! $this->has($id)) { 7. use Exception;
8. if (! class_exists($id)) { 8. use Psr\Container\ContainerExceptionInterface;
9. throw new NotFoundException( 9.
10. "'{$id}' is not a class name and " . 10. final class ContainerException extends Exception
11. "is not set in the container" 11. implements ContainerExceptionInterface
12. ); 12. {
13. } 13. public function __construct(
14. 14. string $message,
15. $this->set(id: $id); 15. ?Exception $previous = null
16. 16. ){
17. return $this->build($id); 17. parent::__construct(
18. } 18. message: $message, previous: $previous
19. 19. );
20. $definition = $this->definitions[$id]; 20. }
21. if ( 21. }
22. is_string($definition->concrete) &&
23. class_exists($definition->concrete)
24. ){
25. $instance = $this->build($definition->concrete); first thing we need to check in our build method is that we
26. if ($definition->singleton) {
can create a reflection object for the requested class, and if
27. $this->addInstance(id: $id, value: $instance);
not, we should throw an exception.
28. }
29.
private function build(string $id): mixed
30. return $instance; {
31. } try {
32. $reflector = new ReflectionClass($id);
33. if ($definition->concrete instanceof Closure) { } catch (ReflectionException $exception) {
34. $result = call_user_func( throw new ContainerException(
35. $definition->concrete, $this "Failed to create object for '{$id}'",
36. ); $exception
37. if ($definition->singleton) { );
38. $this->addInstance(id: $id, value: $result); }
39. } }
40.
41. return $result; If we succeed in creating the reflection, we should now
42. } check if the class is instantiable. We can do this using a handy
43. method provided by the ReflectionClass class.
44. return $definition->concrete;
45. } if (! $reflector->isInstantiable()) {
throw new ContainerException(
"'{$id}' is not instantiable"
);
}
Implementing the Build Method
The final part of our DI Container is the build method. This At this point, we already know that our class can be instan-
is where we implement the “factory” logic of our container, tiated, so the next thing we need to check is whether the class
so this is the backbone of it. Before we start implementing has a constructor defined. If not, we can already instantiate it
it, we are going to use the second exception that the PSR-11 and return the instance.
provides to us. Inside the Exceptions folder, create a file
named ContainerException.php. (See Listing 4)
Let’s start implementing our build method then. If you read
my last article, “An Introduction to Reflection in PHP,” you
will know that one of the use cases for Reflection is creating
a DI Container, and here’s where we are going to use it. The

www.phparch.com \ July 2024 \ 23


Level Up


Listing 5. Listing 5 continued.


1. private function build(string $id): mixed 49.
2. { 50. if (! is_null($parameterType)) {
3. try { 51. $typeName = $parameterType->getName();
4. $reflector = new ReflectionClass($id); 52.
5. } catch (ReflectionException $exception) { 53. if ($parameterType->isBuiltin() &&
6. throw new ContainerException( 54. $typeName === 'array'
7. "Failed to create object for '{$id}'", 55. ){
8. $exception 56. $classArguments[] = [];
9. ); 57.
10. } 58. continue;
11. 59. }
12. if (! $reflector->isInstantiable()) { 60.
13. throw new ContainerException( 61. if (! $parameterType->isBuiltin() &&
14. "'{$id}' is not instantiable" 62. class_exists($typeName)
15. ); 63. ){
16. } 64. $classArguments[] =
17. 65. $this->get($typeName);
18. $constructor = $reflector->getConstructor(); 66.
19. if (is_null($constructor)) { 67. continue;
20. try { 68. }
21. return $reflector->newInstance(); 69. }
70.
22. } catch (ReflectionException $exception) {
71. if ($this->has($parameter->getName())) {
23. throw new ContainerException(
72. $classArguments[] =
24. "Failed to create object for '{$id}'",
73. $this->get($parameter->getName());
25. $exception
74.
26. );
75. continue;
27. }
76. }
28. } 77.
29.
78. $classArguments[] = null;
30. $classArguments = [];
79. }
31. $params = $constructor->getParameters(); 80.
32. foreach ($params as $parameter) { 81. try {
33. $parameterType = $parameter->getType(); 82. return $reflector
34. if (is_null($parameterType) && 83. ->newInstanceArgs($classArguments);
35. ! $parameter->isDefaultValueAvailable() 84. } catch (ReflectionException $exception) {
36. ){ 85. throw new ContainerException(
37. throw new ContainerException( 86. "Failed to create object for '{$id}'",
38. "Failed to create object for '{$id}' - Constructor " . 87. $exception
39. "parameter '{$parameter->getName()}' can't be resolved" 88. );
40. ); 89. }
41. } 90. }
42.
43. if ($parameter->isDefaultValueAvailable()) {
44. $classArguments[] =
45. $parameter->getDefaultValue();
46.
47. continue;
48. }

24 \ July 2024 \ www.phparch.com


Level Up
Understanding and Implementing a Dependency Injection Container - Part II

$constructor = $reflector->getConstructor(); The second type of parameter that we can resolve by its type
if (is_null($constructor)) { is if the parameter is not a built-in type and if that type (class)
try {
return $reflector->newInstance();
exists.
} catch (ReflectionException $exception) { if (! $parameterType->isBuiltin() &&
throw new ContainerException( class_exists($typeName)
"Failed to create object for '{$id}'", ) {
$exception $classArguments[] = $this->get($typeName);
); continue;
} }
}
If we were not able to get the value of the parameter by its
If the class has a constructor, we should then check each
default value or by its type, we can still check if we have the
parameter for it and get the needed values for each of the
definition for that parameter in our DI Container. So we
parameters, meaning that if our class needs instances of other
check by the parameter name, and if it’s registered in our DI
classes, we should then build instances for these classes to be
Container, we get its value from there.
able to build an instance of our class.
If we’re still not able to get the value for the parameter after
The first thing we are going to check is if the parameter has
all these tries, we will just set the value to null.
no type declared and has no default value; we should throw
After getting the values for all the parameters of the
an exception because we’re not able to resolve the param-
constructor, we will try to create an instance of the class with
eter. And for doing that, we are also going to rely on useful
them and return it. With that, we have the full implementa-
methods provided by the ReflectionParameter class.
tion of our build method. (See Listing 5 on the previous page)
$classArguments = [];
$params = $constructor->getParameters();
foreach ($params as $parameter) {
Conclusion
$parameterType = $parameter->getType(); We did it! We implemented a simple, but fully functional
if (is_null($parameterType) && DI Container from scratch, and we can use this container for
! $parameter->isDefaultValueAvailable()
) { simpler use cases without any issues. Along the way, we also
throw new ContainerException( learned how Dependency Injection and a DI Container help
"Failed to create object for '{$id}'" . us to improve the quality of our applications and how we can
" - Constructor parameter " . intelligently use the Reflection API to help us with our DI
"'{$parameter->getName()}' can't be " . Container.
"resolved");
} If you want to check the full implementation of the container
} with a few extras and also see an example of how to use the
container in an application, check this repository I created for
The second thing we are going to check is if the parameter this article: https://phpa.me/github-WendellAdriel1.
has a default value, let’s use it. Do you want to discuss this article, Dependency Injection,
or any other PHP-related topic? Follow me on X: https://x.
if ($parameter->isDefaultValueAvailable()) {
$classArguments[] = $parameter->getDefaultValue(); com/wendell_adriel
continue;
} Wendell Adriel is a conference speaker, tech-
nical writer, open-source enthusiast, amateur
If the parameter has no default value, we are going to check photographer and cat lover. Currently works
if it has a type defined and try to resolve it with that infor- at TrackStreet as a Software Architect and
mation. However, we are able to resolve only two types of in his free time, contributes and maintains
parameters this way. The first one is if the parameter type is some open-source projects and writes
an array because we can then use an empty array for its value. technical content for his blog. Reach him out
on X at @wendell_adriel
if (! is_null($parameterType)) {
$typeName = $parameterType->getName();

if ($parameterType->isBuiltin() &&
$typeName === 'array'
) {
$classArguments[] = [];

continue;
}
}
1 https://phpa.me/github-WendellAdriel

www.phparch.com \ July 2024 \ 25


a php[architect] anthology

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.
Get the Most Out of Your Developers
After reading this book, you’ll understand what processes
work for managing the tasks needed to turn a new feature
or bug into deployable code. But success is more than just
slinging code when you’re in charge, and this book covers
project management and people skills you’ll need to hone.
This book collects almost two-years worth of writings
based on Chris Tankersley’s experience leading develop-
ment teams.
Available in Print, PDF, EPUB, and Mobi.

Order Your Copy


https://phpa.me/devlead-book
RADAR

PHP & WebAssembly: An Edge Case


Matt Lantz
As my career has progressed, I, like many readers out there, have seen the introduction of
some game-changing platforms here and there. The field of web development only remains so
prominent because of these various shifts in how we do things. Among the recent advancements,
WebAssembly (Wasm) stands out as a game-changer, promising near-native performance for web
applications. That alone doesn’t sound like much until you consider the fact that it’s a VM system
running in either the browser or an Edge compute instance near you. So, combining it with PHP, a
stalwart in server-side scripting, can unlock unprecedented efficiencies and capabilities.

What is Webassembly? Real-World Applications


WebAssembly, commonly referred to as Wasm, is a In allthe reading I have doneregarding WebAssembly and
binary instruction format that enables high-performance real-world applications, they often highlight ideas such as
applications to run on web pages. It was designed with increasing performance, moving complex sub-processes to
performance in mind, allowing developers to write code in the client side, more modularity, etc.However, the best cases
multiple languages (C, C++, Rust, and others) and execute I have seen thus far were WordPress implementing a play-
it at near-native speed. By providing a compilation target for ground and Wasmer implementing a serverless platform for
these languages, WebAssembly makes it possible to leverage deploying PHP-based applications.
existing codebases and write new web applications that are WordPress Playground
both fast and capable. https://phpa.me/wordpress-playground1
WebAssembly’s key benefits include: The WP team highlights a method for running WordPress
1. Performance: Near-native execution speed directly in the browser as part of a tinkering space. This plat-
form enables developers to tinker without infrastructure
2. Interoperability: Compatibility with existing JavaS-
configurations or local installations.
cript code
Wasmer Platform
3. Portability: Runs on all modern browsers on any https://wasmer.io/2
platform They provide a complete system for building and deploying
4. Security: Sandboxed execution that helps maintain applications on serverless infrastructure; theyeven handle the
the security of the user environment compiled package registry and a great runtime for developing
applications.
Given that this article is in a PHP magazine, I’ll dispense
with providing some deeper context of PHP’s history and Show Me Some Code.
what it’sbeen optimally usedfor in the past. If you’re intrigued and want to start experimenting with
PHP and WebAssembly, here are some steps to guide you.
Bridging PHP and Webassembly
First, Idon’trecommend compiling PHPon your ownor trying
Historically, PHP hasbeen confinedto server-side to make sense of the out-of-date tutorials out there.WebAs-
executions, and its “serverless” support is limited at best. sembly and PHP are still in the very early stages of integration,
However,the advent ofWebAssembly opens new doors for and most tutorials discussing it are already nearly four
PHP, making it viable to run PHP code on the client side or versions of PHP behind. There are generally two primary use
on any machine that can operate the WebAssembly Virtual cases for using PHP and WebAssembly: one in a browser and
Machine. the other on a serverless system.
There is a general catch, though. By default, WebAssemblyis In either case, Sean Morris appears to be leading much of
sandboxed.Thismeans it has someheavylimitations when the growth in the integration of the two systems, and luckily,
running in the browser, such as connecting to an external the WordPress team has already handled compiling various
database or other resource. When running via NodeJs or versions of PHP into accessible packages.
some other system, you can gain access to system integra- Below is an example of running PHP within the browser in
tion, and details are available athttps://wasi.dev, though none your codebase.As we can see, itscapabilities arerather limited,
of these features have been fully standardized yet. So, forthe
time being, if you’re looking to implement a means of PHP
1 https://phpa.me/wordpress-playground
connecting to a resource, the only method is via sockets.
2 https://wasmer.io/

www.phparch.com \ July 2024 \ 27


RADAR
PHP & WebAssembly: An Edge Case

Listing 1. Listing 2.
1. const PHP = require('php-wasm/PhpWeb').PhpWeb; 1. #!/usr/bin/env node
2. const php = new PHP; 2. import { PhpNode } from 'php-wasm/PhpNode.mjs';
3. 3.
4. // When the engine is ready this event is triggered 4. const php = new PhpNode({
5. php.addEventListener('ready', () => { 5. docroot: '/persist/www',
6. php.run('<?php echo "Hello, world!";'); 6. persist: [
7. }); 7. {
8. 8. mountPath: '/persist',
9. // Handles the script output 9. localPath: './persist'
10. php.addEventListener('output', (event) => { 10. },
11. console.log(event.detail); 11. {
12. }); 12. mountPath: '/config' ,
13. 13. localPath: './config'
14. // Alternative for collecting return Value 14. }
15. php.addEventListener('ready', () => { 15. ]
16. php.run('<?php echo "Hello, world!";') 16. });
17. .then(retVal => { 17.
18. console.log(retVal) 18. // Listen to STDOUT
19. }); 19. php.addEventListener('output', (event) => {
20. }); 20. console.log(event.detail);
21. });
and at first glance, this code seems more complex than neces- 22.
sary.Beyondthat,our PHP “engine” has limited powers and 23. // Listen to STDERR
needs to be processed by JavaScript. The only real gain is 24. php.addEventListener('error', (event) => {
that we do not need PHP to run on the end-user’s computer. 25. console.log(event.detail);
26. });
Much of this exampleis takenfrom Sean Morris’ repository: 27.
https://github.com/seanmorris/php-wasm3.Though I did get 28. await php.run('<?php include("persist/index.php");');
it to function in a Laravel test app on my machine, it was not
fast and felt considerably clunky. (See Listing 1) me with more coding powers and less maintenance, I wish
To me,this ultimately limits the browser-driven use case them all the best in their journey.
to playgrounds and demo spaces for running PHP code via Though I did have a challenging time getting any of these
WebAssembly. systems to run and wascompletelyunable to get the compilers
However, if you wanted to run some PHP-based code on a to process PHP builds as per the documentation, I was able
serverless instance that was running NodeJS, then you could to put together some tiny proofs-of-concept for each of the
utilize something like this: (See Listing 2) leading cases for using PHP in WebAssembly.First, I was able
The above code also uses Sean Morris’ repository, which to process PHP via the browser,and second, I usedNode on a
enables you to load PHP code from an external file and server level, albeitonly on a CLI level.My main takeaway from
have Node process its output.Your PHPcodeis, ofcourse,still all of this was that its use cases are few and far between; what
limited by connectivity and various other quirks, whichI’mnot I mean by this is, of course, I see few use cases where thistype
going to dig into right now, but you can successfully have of deploymentisreally useful.The way in which Sean Morris
Node run PHP if youwantto.However, I diddoa simple was able to initiate a Laravel instance and have it running is
test using file_get_contents to load an RSS feed. Given the pretty neat overall. Still, it will be a while before I consider
sandboxing, it simply spits out an error statement. But, Sean integrating WebAssembly into my coding practices.
does demonstrate running Laravel 11 via Node on his demo
sitehttps://seanmorris.github.io/php-wasm/, which left me Matt has been developing software for over 13
thinking Laravel must have file-writing powers, but instead, it years. He started his career as a PHP developer for
connects to SQLite, and everything is database-driven. Sean a small marketing firm but has since worked with
a few Fortune 500 companies, led a couple teams
also wrote the SQLite driver package. of developers, and is currently a Cloud Architect
PHP CGI on WebAssembly is the larger bet that Wasmeris for a significant Travel technology company.
focusedon as per their documentation and the idea of running He’s contributed to the open-source community
PHP code on any device. Though theideaof running PHP on on projects such as Cordova and Laravel. He
also made numerous packages and has helped
embedded devices does interest me, I cannot help butthink, maintain a few. He’s worked with start-ups and
“Is thisreallythe right technology for this job?”. Thatbeingsaid, sub-teams of big teams within large divisions of
if people want to spend time integrating systems that provide companies. He spends time with his wife and kids
when he’s not tinkering with code or learning new
technologies. @Mattylantz
3 https://github.com/seanmorris/php-wasm

28 \ July 2024 \ www.phparch.com


PHP Foundry

PHPStan’s Watchful Eye


Oscar Merida
Keeping the type hints in argument and return types makes your code easier to understand,
describes intents to colleagues, and prevents bugs from sloppy type coercions. If you’re the lead,
the responsibility can’t fall on your shoulders alone to make sure your team is being diligent about
using them properly. Nor should you be expected to check that code will work properly during
code reviews. In this month’s article, I’ll show you why you need to add PHPStan to your project
and how to get the most out of it by gradually increasing its scrutiny and documenting your types
accurately. We’ll also see how you can extend PHPStan to check for custom rules made to analyze
your code and enforce coding practices.

The Spacetraders client is rapidly growing, but I’ve only a mess of legacy spaghetti code from the before times or only
added under half of the API endpoints documented. Since maintain your current application to be concise and readable.
my focus has been on adding functionality, I have been quite In that case, you should adopt PHPStan into your develop-
lax in the structure of my code, which led to the first redesign ment workflow. Doing so on a previous project helped my
discussed last month. We might start by writing our archi- team immensely while we untangled a convoluted WordPress
tectural approaches and other coding standards and sharing site. Instead of copy-pasted procedural code throughout the
them. However, expecting contributors to stick to an archi- theme files, we ended up with encapsulated classes to inte-
tecture or design by decree isn’t a workable solution. Some grate with external APIs, render pages, and configure site
folks will forget or outright ignore the practices you want to features. It didn’t happen overnight, but PHPStan2 was a cata-
establish within a project. Keeping that documentation accu- lyst for our efforts.
rate and up to date also requires investing time and effort. We
can use a number of tools to help us, such as a static analyzer.
What is Static Code Analysis?
Static code analysis examines source code without running
Behold the Argonath, the Pillars of the Kings! ’ cried it. I may be simplifying things, but you can think that it uses
Aragorn. We shall pass them soon. Keep the boats in [nikic/php-parser] and other libraries3 to trace how your
line, and as far apart as you can! Hold the middle of the code handles potential values of expressions, passes argu-
stream!’ ments to functions, uses return values, and more. Although
you can help PHPStan’s analysis with typehints in doc blocks,
… Great power and majesty they still wore, the silent its analysis is based on the source lines it can figure out the
wardens of a long-vanished kingdom. Awe and fear fell actual types used. It can thus help you find many potential
upon Frodo, and he cowered down, shutting his eyes bugs without writing unit tests or getting your application to
and not daring to look up as the boat drew near. Even run on the server. You can catch many logical errors in your
Boromir bowed his head as the boats whirled by frail and implementation by checking PHPStan’s output early (at least
fleeting as little leaves, under the enduring shadow of the before a commit or push). Some useful ones in my experience
sentinels of Númenor. So they passed into the dark chasm include:
of the Gates.
• Missing argument or return type hints
It can be the guardian at your applications’ gates, but you • Type hints in doc blocks that don’t match the function
don’t need to fear what PHPStan reveals. signature or return type
• Returning null values in cases where the return type is
Static Analysis not nullable
• If a method or function returns null, not handling a
Ondrej Mirtes, creator of PHPStan, wrote about Testing
null return in your calling code.
Strategy With the Help of Static Analysis1 in the April 2018
issue of this magazine. It’s a shame it took me almost two • If you’re not using strict mode, finding method calls
years after that to fully embrace it in my work. that don’t pass the correct argument types.
Using typehints has been a running theme throughout
recent articles in this column. Suppose you need to modernize
2 https://phpstan.org
1 https://phpa.me/phparch-stan 3 https://phpa.me/phpstan-composer

www.phparch.com \ July 2024 \ 29


PHP Foundry
PHPStan’s Watchful Eye

For an intricate look at how this works, watch Jan Tvrdík: levels can find bugs that hide in seldom used code paths, calls
PHPStan Under the Hood – phpCE 2018 to undefined functions, doc blocks that don’t match a meth-
od’s arguments, and inconsistent return values. You may have
dozens to hundreds of such errors with any decently sized
application, but fixing these tidies up your codebase and
Getting Started signals that you’re no longer going to tolerate sloppy code.
Add PHPStan to your project’s dev dependencies and then It also helps familiarize your team with running phpstan,
create a phpstan.neon file to hold configuration settings. understanding its output, and fixing the issues it finds.
For my first run, I started at level 2, where I found 7 issues
compser require --dev phpstan/phpstan that I needed to fix ( See Figure 1). These consisted of places
where I had used @var instead of @param for type hinting
Listing 1 shows a starting configuration file for an initial strings in Value classes, another value object I’d forgotten to
run. You can do so much with this file4. At the moment, I’m define altogether, and a child class that was missing the return
asking it to scan my src/ and cli/ directories at level 1. You type required for one of its methods.
should only analyze the code you write. If PHPStan complains
Figure 1.
that it doesn’t know or recognize classes (“symbols”) in one
of your dependencies, use the scanDirectories or scanFiles
sections. If the dependency is popular, there may be a stubs5
package you can use instead (more on this later).

Listing 1.
1. includes:
2. - vendor/phpstan/phpstan/conf/bleedingEdge.neon
3.
4. parameters:
5. # PHP 8.3
6. phpVersion: 80300 I didn’t get more PHPStan errors until I turned it up to level
7. 5, where it complained about method calls that did not have
8. level: 2 the expected types. I had to fix 19 of these, but most were due
9. to one Trait used in a lot of CLI commands to output values
10. paths: to the screen. (See Figure 2)
11. - src
12. - cli
Figure 2.

Being able to configure the level is an excellent way to intro-


duce PHPStan to a project. Doing so lets you ease its checks
into a complicated codebase by giving you control over what
kinds of issues it reports. At lower levels, the analysis is less
rigorous. As you eliminate the issues for a level, you can grad-
uate to the next one. You can see the full description of all
10 levels in the documentation6. In general, the higher and
stricter levels expect your code to have correct type hints for
arguments and return types and to avoid arrays and stdClass
unless you can describe their shape with custom docblock At this level, basic type hints are not enough. One of the
annotations. errors returned was:
PHPStan offers a way to generate a baseline report so that 65 Method Client::convertResponse()
you can, in theory, start at a higher level. The baseline becomes has no return type specified.
an issue that will be ignored in future runs. The theory is that
your new code can be held to a higher standard than what
you have. I prefer to fix all the issues in your code, especially Let’s look at that method. Yup, it returns a value, but the
the code that was written for PHP7 and earlier. The first three signature doesn’t indicate what it would return. The static
analyzer knows from the return keyword but doesn’t have
enough information to properly analyze what the calling
4 https://phpstan.org/config-reference code does with the return value in further calls. You and I
5 https://phpstan.org/user-guide/stub-files can deduce from variable names and method calls that
6 https://phpstan.org/user-guide/rule-levels

30 \ July 2024 \ www.phparch.com


PHP Foundry
PHPStan’s Watchful Eye

$responseClass must be the name of a class in our application Instant Feedback


that must have a fromArray() method. (See Listing 2)
Switching to the terminal every couple of minutes is a pain.
Listing 2. You may fall out of the habit if you don’t analyze code often.
Piling up a daunting list of PHPStan errors to wade through
1. use \GuzzleHttp\Psr7\Response; can be discouraging. You might end up not using static
2. analysis in the long run. PHPStorm can integrate PHPStan’s
3. protected function convertResponse(
feedback into your IDE with one configuration change. Open
4. Response $response,
5. string $responseClass
Settings, then go to PHP, Quality Tools. Expand the section
6. ){ for PHPStan, click the three dots, and on the next screen,
7. $json = $this->decodeResponse($response); enter the path the PHPStan executable, which should be in
8. return $responseClass::fromArray($json['data']); your project’s vendor/bin directory.
9. } Don’t forget to click on “PHPStan Inspection” and check
the box to have it run in the background. On the right side
of the dialog (Figure 3), you can control at what severity to
show the errors. I like setting it to Error or Warning. When
PHPStan needs some docblock comments to capture that the static analyzer finds an issue in the file you’re working on,
information. See the PHPDoc Types7 documentation for a you’ll see it in the “Problems” tab. Depending on the severity
complete list of these. They can describe arrays and generics, that you selected, you can also get a red underscore on the edit
indicate conditional returns, and more. We’ve seen some of page. Hovering over it will show error details as in Figure 4.
these when describing data returned by the Spacetraders API
for Valinor to map the raw values to value objects. In this case, Figure 3.
we might use the class-string for $responseClass. But any
old class name won’t do. Since it has to be a class that extends
Phparch\SpaceTraders\Response\Base, we can include the
class name in angle brackets. classs-string<T>. We use the @
template tag to document the kinds of classes we accept. With
the comments shown below, we’ve done more than remove
one error from the report. PHPStan has deeper insight into
how our code works and future scans of new code could
indicate an error if we pass a class name that doesn’t extend
Response\Base.
In fact, on the next run, PHPstan found that one of my
client calls was trying to use a class that didn’t extend the base
class—all done without tests or making API requests during
execution. These three added doc block lines have saved
Future Me from hunting down why that call didn’t work and
prevented Future Me from making that same mistake when
writing new methods. (See Listing 3)
Figure 4.

Listing 3.
1. /**
2. * @template T of Response\Base
3. * @param class-string<T> $responseClass
4. * @return T
5. */
6. protected function convertResponse(
7. \GuzzleHttp\Psr7\Response $response,
8. string $responseClass
9. )
10. {

7 https://phpa.me/phpstan-doc-types

www.phparch.com \ July 2024 \ 31


PHP Foundry
PHPStan’s Watchful Eye

How High? a string that is not listed. Potentially, this catches a bug that
would be difficult to trace in production. (See Listing 5)
I’ve ended with a phpstan.neon field set to scan my applica-
tion at Level 8 with zero errors. At that level, my work was to Listing 5.
ensure my methods gracefully handled null values from user
input. Granted, at the moment, it’s a relatively small codebase, 1. /**
but that’s only one away from PHPStan’s maximum setting. I 2. * @param ISO3166::KEY_ALPHA3|ISO3166::KEY_ALPHA2 $key
can catch bugs earlier by regularly running it and fixing the 3. * @return array{'city':string, 'region':string,
errors it highlights. We also need to practice using its doctype 4. 'country':string}
5. */
extensions to describe method arguments and return values
6. public static function sanitizeCityRegionCountry(
and avoid using mixed as a shortcut for making errors go away. 7. ?string $city,
I might want to jump to level 9 soon since it is limited using 8. ?string $region,
mixed. 9. ?string $country,
10. string $key = ISO3166::KEY_ALPHA3
Array Shapes 11. ): array {

I’ve avoided working with bare arrays by using Valinor to


map API responses to Value Objects. Sometimes that’s not
possible or easy to do. If your functions expect arrays as
arguments or return values, use PHPStan’s array shapes to
There is much more you can do with doc blocks to better
document what they contain until you can rewrite them to
document the types used in your system. You can define
use objects.
array-shapes as pseudo-classes in one file, import and use
Consider a helper function for logging errors smarter_log()
those definitions in other files, and use object-shapes to
with the signature shown below. In PHP, we can only type
describe dynamic classes like stdClass. Bookmark the type
hint that $context is some array. We can detail that it should
hints section for when PHPStan complains about an array not
contain the docblock comment using array{}. First, we indi-
having a type or matching the expected shape.
cate a key, ?: indicates optional keys. If a key is required, use :.
The right side of each indicates the value’s type. Ideally, you’d
avoid mixed in here but this is better than array. In PHPStorm, Writing Custom Rules
these shapes become autocomplete hints, which is another We’re not limited to having PHPStan only check our types
bulwark against typos. (See Listing 4) and doc blocks. You can write custom rules, which are classes
added to your PHPStan configuration, in a section called
Listing 4. “Rules”. Listing 6 (next page) shows one such rule that imple-
1. /** ments the \PHPStan\Rules\Rule interface. I added it to prevent
2. * @param array{line?: string, file?: string, using trigger_error(), session_*(), and other unwanted
3. trace?: bool, data?:mixed } $context function calls. The getNodeType() method tells PHPStan what
4. */ kinds of things (nodes) to inspect, in this case, any function
5. function sma_log( call. When it encounters a function call, the processNode()
6. string $message, method checks if the name of the function is on our block list
7. array $context = [], and adds an error message if it is.
8. Monolog\Level $level = \Monolog\Level::Warning,
9. bool $addBacktrace = false rules:
10. ): void { - SpaceTraders\Phpstan\ForbiddenFunctionsRule

The Custom Rules8 documentation lists all the types of


The docblock below shows how to document an array that’s nodes that PHPStan can inspect.
returned from a function call. For return shapes, you don’t
need a variable name. In this case, the function returns three
string values with the city, region, and country keys. It uses Frameworks and Applications
the League package to normalize the address components, If you’re using a popular PHP framework or content
and the $key parameter should only be one of two possible management system, the surrounding community likely has
constants from that library. While the PHP typehint is limited contributed packages to help PHPStan in analyzing it. Most
to string, the docblock @param can list allowed values delim- should be one composer require statement away from instal-
ited by a pipe character. If any key constant was allowed, we lation.
could shorten it to ISO3166::*. Now, when we run PHPStan,
we’ll be notified if some code tries to call this method with
8 https://phpa.me/phpstan-rules

32 \ July 2024 \ www.phparch.com


PHP Foundry
PHPStan’s Watchful Eye
Listing 6.
1. namespace Phparch\SpaceTraders\Phpstan;
• Larastan is a wrapper for Laravel that helps it support
2. that frameworks “magic.” https://github.com/larastan/
3. use PhpParser\Node; larastan9
4. use PhpParser\Node\Expr\FuncCall; • phpstan-symfony adds stubs and rules to explain
5. use PHPStan\Analyser\Scope; how Symfony components work. https://github.com/
6. use PHPStan\Rules\Rule;
phpstan/phpstan-symfony10
7. use PHPStan\Rules\RuleErrorBuilder;
8. • phpstan-laminas-framework describes how the DI
9. /** container works, and documents how magic calls work
10. * @implements \PHPStan\Rules\Rule<Node\Expr\FuncCall> in controllers, and more. https://phpa.me/phpstan-lam-
11. */ inas11
12. class ForbiddenFunctionsRule implements Rule
13. { • cakephp-phpstan provides services and rules descrip-
14. /** tions for CakePHP https://github.com/CakeDC/
15. * Key is func to block, string is error to show cakephp-phpstan12
16. * @var array<string, string> • The Drupal project uses drupal-phpstan so that it can
17. */ find and understand classes in core and contributed
18. private array $blocklist = [ modules. https://phpa.me/drupal-phpstan-start13
19. // what about error_log
20. 'trigger_error' => • phpstan-wordpress provides stubs that PHPStan can
21. 'Cannot call trigger_error, use ' . use to infer how WordPress core works. https://phpa.
22. 'smarter_log() or throw an exception.', me/phpstan-wordpress14 Popular packages have separate
23. 'eval' => stubs packages you can install provided by php-stubs
24. 'eval() not allowed.', including:- Advanced Custom Fields https://github.com/
25. 'join' => php-stubs/acf-pro-stubs15
26. 'join() is an alias, use implode()',
27. 'session_start' => • WooCommerce https://phpa.me/woocom-
28. 'session_start() should not be used', merce-stubs16
29. 'session_commit' => • GravityForms https://phpa.me/gravity-form-stubs17
30. 'session_commit() should not be used',
31. 'session_id' =>
32. 'session_id() should not be used', Conclusion
33. 'session_name' =>
There’s no practical reason not to make PHPStan part of
34. 'session_name() should not be used',
35. 'session_write_close' => your code reviews today. In a future article, I’ll share how to
36. 'session_write_close() should not be used', add it as part of your git push hooks and CI pipeline. Start at
37. ]; a low level. Don’t get discouraged if it takes weeks to reach
38. levels 2, 3, or 4, particularly if you have a large team or a
39. public function getNodeType(): string mature application. I promise you that you will see gains in
40. { code clarity and catch some bugs earlier with its helpful, ever-
41. //only nodes of this type trigger processNode() present eye. Just make sure someone runs it regularly if you
42. return \PhpParser\Node\Expr\FuncCall::class;
can’t automate it from the start.
43. }
44.
45. public function processNode( Oscar Merida has been working with
46. Node $node, Scope $scope PHP since version 4 was released and is
47. ): array { constantly learning new things about it and
48. // the name of a method call *could* be a still remembers installing an early version
49. // variable or a string on Apache. When not coding or writing, he
50. if ($node->name instanceof Node\Name) { enjoys RPGs, soccer, and drawing. @omerida
51. $name = $node->name->toString();
52. if ($this->blocklist[$name]) { 9 https://github.com/larastan/larastan
53. $msg = RuleErrorBuilder::message( 10 https://github.com/phpstan/phpstan-symfony
54. $this->blocklist[$name]
11 https://phpa.me/phpstan-laminas
55. )->build();
56. return [$msg]; 12 https://github.com/CakeDC/cakephp-phpstan
57. } 13 https://phpa.me/drupal-phpstan-start
58. } 14 https://phpa.me/phpstan-wordpress
59.
60. return []; 15 https://github.com/php-stubs/acf-pro-stubs
61. } 16 https://phpa.me/woocommerce-stubs
62. } 17 https://phpa.me/gravity-form-stubs

www.phparch.com \ July 2024 \ 33


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
Readable Code

Securing PHP Applications: A Deep


Dive - Part Three - Authentication And
Authorization
Christopher Miller
So far, we’ve examined some of the attacks for a hacker, as well as some of the thoughts around
code—but now, let’s consider Authentication and Authorization: the first layer of defense on any
active site. One overriding thought should ALWAYS be “don’t roll your own authentication”—in
other words, people have spent a long, long time building this out. The examples here are purely
informational and should be steps to consider—and should be treated as pseudo-code.

within their code, enforcing secure password practices and


PHP User Authentication and reducing the risk of breaches due to weak passwords.
Authorization: Forging Fortresses of Enforcing Password Complexity
Access Control To effectively implement strong password policies, devel-
In the domain of PHP application security, user authenti- opers can use PHP to check passwords against a set of
cation and authorization are the bedrock upon which digital predefined rules. These rules typically include a minimum
fortresses are built. This section navigates the intricate land- length requirement and the inclusion of both uppercase and
scape of PHP user access, emphasizing robust password lowercase characters, numbers, and special symbols. Let’s
policies, multi-factor authentication (MFA), role-based explore an example where PHP developers enforce password
access control (RBAC), and the importance of regular review policies by checking for a minimum length and the inclusion
and updating of user permissions. of both uppercase and lowercase characters. (See Listing 1)
In this example, the password policy ensures that any
Implementing Strong Password Policies in PHP: user-provided password is at least 8 characters long and
Fortifying the First Line of Defense contains both uppercase and lowercase letters. This is a basic
The guardianship of PHP applications begins with robust yet effective way to enforce password complexity. However,
password policies that serve as the first line of defense against more advanced checks can be added to further strengthen
unauthorized access. Ensuring that users create strong, password security, such as requiring at least one numeric
complex passwords is crucial for maintaining the security digit and one special character. (See Listing 2)
of any web application. PHP developers have the ability
Listing 2.
to specify stringent password complexity requirements
1. // Enhanced PHP Password Policy Enforcement Example
Listing 1. 2. $rawPassword = 'SecurePassword123!';
3.
1. // PHP Password Policy Enforcement Example 4. if (strlen($rawPassword) < 8 ||
2. $rawPassword = 'SecurePassword123'; 5. !preg_match('/[a-z]/', $rawPassword) ||
3. 6. !preg_match('/[A-Z]/', $rawPassword) ||
4. if (strlen($rawPassword) < 8 || 7. !preg_match('/[0-9]/', $rawPassword) ||
5. !preg_match('/[a-z]/', $rawPassword) || 8. !preg_match('/[\W]/', $rawPassword)) {
6. !preg_match('/[A-Z]/', $rawPassword)) { 9. // Password does not meet complexity requirements
7. // Password does not meet complexity requirements 10. echo 'Password must be at least 8 characters ' .
8. echo 'Password must be at least 8 characters ' . 11. 'long and include uppercase letters, ' .
9. 'long and include both ' . 12. 'lowercase letters, a number, and ' .
10. 'uppercase and lowercase letters.'; 13. 'a special character.';
11. } else { 14. } else {
12. // Password meets complexity requirements, 15. // Password meets complexity requirements,
13. // proceed with authentication 16. // proceed with authentication
14. // ... 17. // ...
15. } 18. }

www.phparch.com \ July 2024 \ 35


Readable Code
Securing PHP Applications: A Deep Dive - Part Three - Authentication And Authorization

Educating Users On Password Security Best By combining these strategies, PHP developers can create a
Practices robust framework for password security, protecting users and
applications from unauthorized access and potential breaches.
While enforcing strong password policies is crucial,
Implementing strong password policies and educating users
educating users on password security best practices is equally
about best practices are fundamental steps in fortifying the
vital. PHP developers can integrate guidance within the PHP
first line of defense in web application security.
application to help users understand the importance of secure
passwords. This can include advice on using unique pass- Multi-Factor Authentication (mfa) for PHP
words, avoiding common patterns, and regularly updating Security: Elevating the Authentication Citadel
credentials.
Multi-factor authentication (MFA) serves as a formidable
// PHP Password Security Best Practices Guidance defense against unauthorized access by adding an extra layer
echo 'For enhanced security, use a unique password, ' . of verification. It ensures that even if an attacker gains access
'avoid common patterns such as "123456" or ' . to a user’s password, they still need an additional factor to
'"password", and regularly update your' .
breach the account. PHP developers can enable MFA features
'credentials. Consider using a password manager ' .
'to generate and store strong passwords securely.'; within their applications, prompting users to set up and use
additional authentication methods. This guide will demon-
Providing this guidance within the application can help strate how to implement MFA in a PHP application using the
users make informed decisions about their password choices, Google Authenticator library.
further enhancing the overall security of the application.
Setting Up Mfa
Comprehensive Password Policy Implementation To integrate MFA into your PHP application, you first
A comprehensive password policy implementation in a need to generate a secret key for each user and provide a
PHP application should also consider the following best prac- way for them to set up their MFA device, typically via a QR
tices: code. Here’s an example of how to generate a QR code using
1. Preventing Password Reuse: Ensure users cannot the Google Authenticator library and QuickChart API. (See
reuse their previous passwords. This can be achieved Listing 4)
by storing a hash of old passwords and comparing
them when a user tries to change their password. Listing 4.
2. Enforcing Regular Password Changes: Require users 1. // PHP Multi-Factor Authentication (MFA) Setup Example
to change their passwords periodically to reduce the 2. // Include Google Authenticator library
risk of long-term exposure in case of a breach. 3. require 'vendor/autoload.php';
4.
3. Account Lockout Mechanism: Implement an account 5. use PHPGangsta_GoogleAuthenticator;
lockout mechanism after a certain number of failed 6.
login attempts to prevent brute force attacks. 7. $authenticator = new PHPGangsta_GoogleAuthenticator();
4. Multi-Factor Authentication (MFA): Enhance 8. // Generate a unique secret key for the user
security by integrating multi-factor authentication, 9. $secretKey = $authenticator->createSecret();
10. $user = 'User123'; // Example user identifier
requiring users to provide additional verification 11.
besides the password. (See Listing 3) 12. // Generate QR code URL for MFA setup
13. $qrCodeUrl = 'https://quickchart.io/qr?text=' .
Listing 3. 14. urlencode(
15. "otpauth://totp/PHPApp:$user?secret=$secretKey"
1. // Example of Account Lockout Mechanism 16. );
2. 17.
3. // This should be tracked per user and stored in a db 18. echo '<img src="' . $qrCodeUrl .
4. $failedAttempts = 0; 19. '" alt="QR Code for MFA setup">';
5.
20. echo '<p>Scan this QR code with your MFA app.</p>';
6. if ($failedAttempts >= 5) {
7. echo 'Your account has been locked due to ' .
8. 'too many failed login attempts. Please ' .
9. 'try again later or contact support.'; Verifying Mfa During Login
10. // Implement logic to lock account and notify user Once the user has set up MFA, they will enter a time-based
11. } else { one-time password (TOTP) generated by their authenticator
12. // Proceed with authentication and increment app during login. Your PHP application needs to verify this
13. // $failedAttempts if the login fails TOTP against the secret key stored for the user.
14. } (See Listing 5)

36 \ July 2024 \ www.phparch.com


Readable Code
Securing PHP Applications: A Deep Dive - Part Three - Authentication And Authorization

Listing 5. Listing 6.
1. // PHP (MFA) Verification Example 1. // Full Example: Setting Up and Verifying MFA
2. // Include Google Authenticator library 2. // Include Google Authenticator library
3. require 'vendor/autoload.php'; 3. require 'vendor/autoload.php';
4. 4.
5. use PHPGangsta_GoogleAuthenticator; 5. use PHPGangsta_GoogleAuthenticator;
6. 6.
7. $authenticator = new PHPGangsta_GoogleAuthenticator(); 7. // Step 1: Generate and Store Secret Key
8. // Retrieve the stored secret key for the user 8. $authenticator = new PHPGangsta_GoogleAuthenticator();
9. $secretKey = 'stored_secret_key_for_user'; 9. // Generate a unique secret key for the user
10. 10. $secretKey = $authenticator->createSecret();
11. // Assume the user has submitted the MFA code 11. $user = 'User123'; // Example user identifier
12. // via a POST request 12. // Store $secretKey securely in the
13. // User-entered MFA code 13. // database associated with $user
14. $enteredCode = $_POST['mfa_code']; 14.
15. 15. // Step 2: Display QR Code for Setup
16. // Verify the entered code 16. $qrCodeUrl = 'https://quickchart.io/qr?text=' .
17. $isCodeValid = $authenticator->verifyCode( 17. urlencode(
18. $secretKey, $enteredCode, 2 18. "otpauth://totp/PHPApp:$user?secret=$secretKey"
19. ); // 2 = 2*30sec clock tolerance 19. );
20. 20. echo '<img src="' . $qrCodeUrl . '"
21. if ($isCodeValid) { 21. alt="QR Code for MFA setup">';
22. // MFA code is correct, proceed with authentication 22. echo '<p>Scan this QR code with your MFA app.</p>';
23. echo 'MFA code is correct. Access granted.'; 23.
24. // ... 24. // Step 3: Verify MFA Code During Login
25. } else { 25. // Assume the user has submitted the MFA code
26. // MFA code is incorrect, deny access 26. // via a POST request
27. echo 'Invalid MFA code. Access denied.'; 27. // User-entered MFA code
28. } 28. $enteredCode = $_POST['mfa_code'];
29.
Comprehensive Mfa Implementation 30. // Retrieve the stored secret key for the user
31. // $secretKey = getStoredSecretKeyForUser($user);
Implementing MFA in your PHP application involves 32.
several key steps to ensure robust security: 33. $isCodeValid = $authenticator->verifyCode(
1. Generate and Store Secret Keys Securely: Each user 34. $secretKey, $enteredCode, 2
35. ); // 2 = 2*30sec clock tolerance
should have a unique secret key generated and stored 36.
securely in the database. Ensure this key is kept confi- 37. if ($isCodeValid) {
dential and protected. 38. // MFA code is correct, proceed with authentication
2. Display QR Code for Setup: Provide users with a QR 39. echo 'MFA code is correct. Access granted.';
code that they can scan using an authenticator app, 40. // ...
such as Google Authenticator, to set up their MFA. 41. } else {
42. // MFA code is incorrect, deny access
3. Verify Codes During Login: During the login 43. echo 'Invalid MFA code. Access denied.';
process, prompt users to enter the TOTP generated 44. }
by their authenticator app and verify it against the
stored secret key. By integrating MFA into your PHP application, you elevate
4. Handle Backup Codes: Offer users backup codes that the security of your authentication process, creating a more
they can use if they lose access to their authenticator formidable defense against unauthorized access. Combining
app. Ensure these codes are also securely generated MFA with strong password policies and user education
and stored. ensures a robust, multi-layered security approach.
5. Educate Users: Provide clear instructions on how to Role-Based Access Control (rbac) to Restrict
set up and use MFA, highlighting its importance for
Unauthorized PHP Access: Crafting Digital
enhancing account security.
Permissions
6. Graceful Error Handling: Implement graceful error
handling and user feedback to guide users through Role-Based Access Control (RBAC) empowers PHP devel-
the MFA process, especially when codes are incorrect opers to design granular access control models within their
or expired. (See Listing 6) applications, ensuring users have access only to the resources
necessary for their roles. By defining roles and permissions,

www.phparch.com \ July 2024 \ 37


Readable Code
Securing PHP Applications: A Deep Dive - Part Three - Authentication And Authorization

developers can streamline authorization processes and Assigning Roles to Users


enhance security. Regular reviews of user roles and permis- Roles must be assigned to users when created or updated
sions are essential to ensure that access remains aligned with in the system to make the RBAC system effective. Here’s an
organizational requirements and security policies. Let’s delve example of how to assign roles to users and store these assign-
into an example where PHP developers define roles and grant ments in a database: (See Listing 8)
permissions.
Defining Roles and Permissions Listing 8.
In an RBAC system, roles are defined with specific permis-
sions associated with each role. Users are then assigned roles 1. // Example user data retrieved from the database
based on their responsibilities. Here’s an example illustrating 2. $user = [
how to define roles and assign permissions in a PHP applica- 3. 'username' => 'john_doe',
4. // This should be stored securely in the user db
tion: (See Listing 7)
5. 'role' => 'editor',
6. ];
Listing 7. 7.
8. // Retrieve the user role
1. // PHP Role-Based Access Control (RBAC) Example 9. $userRole = $user['role'];
2. $rolePermissions = [ 10.
3. 'admin' => 11. // Assign a new role to a user
4. ['manage_users', 'edit_content', 'delete_content'], 12. // (this would typically be done
5. 'editor' => ['edit_content'], 13. // through an admin interface)
6. 'viewer' => ['view_content'], 14. function assignRole($username, $newRole) {
7. ]; 15. // Update the user's role in the db (pseudo-code)
8. 16. // updateUserRoleInDatabase($username, $newRole);
9. // Simulating retrieving the user role from the db 17. echo "Role of $username updated to $newRole.";
10. $userRole = 'admin'; // Example user role 18. }
11. 19.
12. // Function to check if a user has a specific permission 20. // Example of assigning a new role to a user
13. function hasPermission( 21. assignRole('john_doe', 'admin');
14. $rolePermissions, $userRole, $permission
15. ){
16. return in_array(
17. $permission, $rolePermissions[$userRole]
18. );
19. }
Implementing Role-based Access Control in
20. Actions
21. // Check if the user has the 'edit_content' permission Integrate role checks into various actions within your
22. if (hasPermission(
application to ensure that only authorized users can perform
23. $rolePermissions, $userRole, 'edit_content'
24. )) {
sensitive operations. Here’s how you can enforce RBAC
25. // User has permission, proceed with the action checks in different parts of your application: (See Listing 9)
26. echo 'Permission granted. You can edit content.';
27. // ... Listing 9.
28. } else {
29. // User does not have permission, deny access 1. // Simulated action requiring 'delete_content' perms
30. echo 'Access denied. Insufficient permissions.'; 2. $action = 'delete_content';
31. } 3.
4. if (hasPermission(
5. $rolePermissions, $userRole, $action)
6. ){
7. // User has permission to perform the action
8. echo "Permission granted. You can $action.";
In this example, the $rolePermissions array defines the 9. // Perform the delete content action
permissions associated with each role. The hasPermission 10. } else {
function checks if a user’s role includes a specific permission. 11. // User does not have perms to perform the action
12. echo "Access denied. No permission to $action.";
When a user tries to perform an action, the application veri-
13. }
fies if the user’s role grants the necessary permissions before
proceeding.

38 \ July 2024 \ www.phparch.com


Readable Code
Securing PHP Applications: A Deep Dive - Part Three - Authentication And Authorization

Listing 10. Listing 11.


1. // Log access attempts for auditing purposes 1. // Full PHP RBAC Implementation Example
2. function logAccessAttempt( 2.
3. $username, $action, $isGranted 3. // Define role permissions
4. ){ 4. $rolePermissions = [
5. $status = $isGranted ? 'granted' : 'denied'; 5. 'admin' =>
6. $logEntry = date('Y-m-d H:i:s') . " - User " . 6. ['manage_users', 'edit_content', 'delete_content'],
7. "'$username' attempted to $action. " . 7. 'editor' => ['edit_content'],
8. "Access $status." . PHP_EOL; 8. 'viewer' => ['view_content'],
9. file_put_contents( 9. ];
10. 'access_log.txt', $logEntry, FILE_APPEND 10.
11. ); 11. // Simulate retrieving user data from the database
12. } 12. $user = [
13. 13. 'username' => 'john_doe',
14. // Example of logging an access attempt 14. 'role' => 'editor', // Example role
15. $username = 'john_doe'; 15. ];
16. $action = 'delete_content'; 16.
17. $isGranted = hasPermission( 17. // Check if a user has a specific permission
18. $rolePermissions, $userRole, $action 18. function hasPermission(
19. ); 19. $rolePermissions, $userRole, $permission
20. logAccessAttempt($username, $action, $isGranted); 20. ){
21. return in_array(
22. $permission, $rolePermissions[$userRole]
23. );
}
Regular Reviews and Audits 24.
25.
Regular reviews and audits of roles and permissions are 26. // Function to log access attempts
crucial to maintaining a secure RBAC system. Here’s an 27. function logAccessAttempt(
example of a function that audits and logs access attempts, 28. $username, $action, $isGranted
helping administrators keep track of permission usage and 29. ){
potential security issues: (See Listing 10) 30. $status = $isGranted ? 'granted' : 'denied';
31. $logEntry = date('Y-m-d H:i:s') . " - User " .
Comprehensive Implementation 32. "'$username' attempted to $action. " .
Combining these elements, you can create a comprehen- 33. "Access $status." . PHP_EOL;
34. file_put_contents(
sive RBAC system that effectively controls access within your
35. 'access_log.txt', $logEntry, FILE_APPEND
PHP application. Here’s a full example bringing everything 36. );
together: (See Listing 11) 37. }
38.
By implementing RBAC, PHP developers can create a 39. // Example action and permission check
robust framework for managing user permissions, thus 40. $action = 'edit_content';
ensuring that only authorized users can access or modify 41. $userRole = $user['role'];
sensitive resources. This enhances security, aligns access with 42. $isGranted = hasPermission(
organizational policies, and helps maintain the integrity of 43. $rolePermissions, $userRole, $action
44. );
the application. 45.
Regularly Reviewing and Updating PHP User 46. // Log the access attempt
47. logAccessAttempt(
Permissions: the Sentinel’s Watchful Eye 48. $user['username'], $action, $isGranted
Ensuring the effectiveness of PHP user access control 49. );
goes beyond the initial setup; it requires vigilant oversight 50.
and proactive management. Conducting regular PHP user 51. if ($isGranted) {
52. // User has permission to perform the action
access reviews on a predetermined schedule is crucial to
53. echo "Permission granted. You can $action.";
ensure that access permissions align with the organization’s 54. // Perform the edit content action
evolving needs and remain secure against potential threats. 55. } else {
PHP developers can leverage tools and automation to stream- 56. // User does not have perms to perform the action
line the permission management process. Let’s explore a PHP 57. echo "Access denied. No permission to $action.";
example where permissions are reviewed and updated based 58. }
on organizational changes.

www.phparch.com \ July 2024 \ 39


Readable Code
Securing PHP Applications: A Deep Dive - Part Three - Authentication And Authorization

Listing 12. Implementing Automated Permission Reviews


To maintain robust security practices, PHP developers
1. // PHP Automated User Permission Review Example can automate the process of reviewing and updating user
2. // Define the review schedule permissions at specified intervals. Here’s how you can set up
3. $reviewSchedule = 'monthly'; an automated permission review system in PHP:
4.
5. // Check if it's time for a permission review (See Listing 12 on the next page)
6. function time_for_review(
7. $lastReviewDate, $reviewSchedule Key Components of Automated Permission
8. ){
9. // Logic to determine if it's time for a review Review
10. // based on the schedule
1. Review Schedule Definition: Define a schedule
11. // For example, compare current date with
12. // last review date and schedule
(e.g., monthly, quarterly) for conducting permission
13. return true; // Placeholder logic for demonstration reviews. This ensures that reviews are conducted at
14. } regular intervals to keep permissions up-to-date.
15. 2. Automated Review Logic: Implement logic (time_for_
16. // Function to automate the permission review process review function) to determine if it’s time for a review
17. function automate_permission_review() {
based on the specified schedule and the last review
18. // Logic to automate the review process
19. echo "Automating permission review process...\n";
date. Adjust this logic based on organizational needs
20. } and review frequency.
21. 3. Automation Process: Automate the review process
22. // Update permissions based on organizational changes (automate_permission_review function) to streamline
23. function update_permissions_based_on_changes() { the checking and updating of permissions. This can
24. echo "Updating permissions based changes...\n";
involve querying user roles, permissions, and organi-
25. }
26. zational changes from a database or directory service.
27. // Example implementation 4. Update Permissions: Implement logic (update_
28. // Example: last review date permissions_based_on_changes function) to update
29. $lastReviewDate = strtotime('2023-01-01'); permissions based on recent organizational changes.
30. $currentDate = time(); // Example: current date This could include adding new permissions, revoking
31.
outdated permissions, or adjusting roles as per orga-
32. if (time_for_review($lastReviewDate, $reviewSchedule))
33. { nizational policies.
34. automate_permission_review();
35. update_permissions_based_on_changes(); Example Scenario: Updating Permissions
36. // Update the last review date to current date
Imagine an organization that promotes an employee from
37. $lastReviewDate = $currentDate;
38. } a standard user role to a manager role. With an automated
permission review system in place, PHP developers can
ensure that the user’s permissions are updated accordingly:
(See Listing 13)

Listing 13.
1. // Example: Updating permissions for a promoted user
2. function promote_user_to_manager($username) {
3. // Update user role and permissions in the db
4. echo "Promoting user '$username' to manager...\n";
5. }
6.
7. // Assume a user 'alice' is promoted to a manager role
8. $username = 'alice';
9. promote_user_to_manager($username);
10. update_permissions_based_on_changes();

40 \ July 2024 \ www.phparch.com


Readable Code
Securing PHP Applications: A Deep Dive - Part Three - Authentication And Authorization

Benefits of Automated Permission Reviews In 1983, Christopher was introduced to


computers by his dad, at the tender age
• Consistency: Ensures that permission reviews are of 3. now, over 40 years later, he has been
conducted regularly and consistently, reducing the risk working in the industry for over 20 years
of oversight or human error. making an impact across multiple sectors of
the industry. Starting with launching the first
• Efficiency: Automates repetitive tasks, saving time and
web development company in Staffordshire,
effort for PHP developers and administrators. Christopher dealt with the web - when the
• Security: Helps maintain a strong security posture by web was little more than just pretty text. He
promptly updating permissions in response to organiza- established the websites for many different
tional changes, such as role promotions or departmental businesses in their first inception, before
reassignments. moving onto web applications a little while
later. Illness prevented Christopher from
By implementing automated permission reviews in PHP working in the industry full time for some
applications, developers enhance security practices and considerable time - but recovery meant he
ensure that access controls remain robust and aligned with could tackle once again the joys of code - but
organizational requirements over time. Regular reviews and he soon found that his skills had become
updates safeguard against unauthorized access and miti- out of date, so thanks to the School of Code
gate potential risks associated with outdated permissions. in the UK he was able to return to the
This proactive approach strengthens the overall security workplace with revitalised skills ready to
framework of PHP applications, promoting a secure digital tackle the next wave. Specialising since the
School of Code in Readable Code, He has
environment for users and administrators alike.
worked with a large number of languages,
So there you go—the four keys to authentication and autho- specialising in supporting businesses to grow
rization: strong passwords, MFA, RBAC, Regular Reviews. standards for their code base, and now he is
Next month we’ll delve more into incident management and ready to share his processes with the world.
educating users. It’s not as complicated as it sounds, honestly! @ccmiller2018

www.phparch.com \ July 2024 \ 41


Driving Abandonment
Beth Tucker Long
Throughout my 20 years working with PHP, I have always Why is PHP losing market share? I have been posing this
been able to count on two things: the amazing PHP commu- question to people in different industries and at different
nity and that PHP is being replaced by {insert language}. levels. Some say it’s because PHP is not a “real” programming
While I wholeheartedly believe our community is the best, I language, but that has been an argument for 20+ years, so
have never paid much attention the rest as it has been consis- unlikely to only be affecting usage in the last few years. I’ve
tently unfounded and wrong. PHP has always had a special heard that it’s because PHP is not secure, but the major secu-
purpose and focus. It was never difficult to explain the bene- rity breaches over the last 5+ years have not been caused by
fits of PHP to convince clients to use it. PHP. In fact, the security of PHP has only gotten stronger in
That is starting to change, though, and while I’m not super the last decade.
worried about it yet, I am feeling the strain that this shift Then, there is the argument that PHP is not a “modern
is creating. PHP is losing its uniqueness. It used to be very language” following academic best practices. This doesn’t
human-readable and easy to learn. It used to be very flexible. track, though, as the decline in usage started after the release
You could build things quickly. It used to be our “ball of nails” of PHP 8, arguably the strictest and most academical-
- it’s not pretty, but when you throw it, it sticks (thank you, ly-aligned version ever.
Terry Chay1, for this perfect analogy). PHP has never been What’s left? PHP is losing its uniqueness. PHP is a long way
the most academic language, the strictest language, or the from being the most academically correct lanauge. It is so
prettiest language. It has always been the rough-and-tumble hamstrung by shared hosting companies that it will never be
language of getting things done. regarded as the most secure language. PHP doesn’t have the
That has been subtly shifting over the years. In response backing of a major corporation with a huge marketing depart-
to widespread criticisms about PHP’s security and profes- ment, so it will never be the “most enterprise” language. With
sionalism, the core team has been earnestly updating PHP all of the new language restrictions, shortened commands,
to follow academic best practices. I have heard people toss and condensed structure, it is neither the easiest to learn nor
around claims like “PHP is growing up” and “PHP is finally the most human-readable anymore.
becoming an enterprise-level programming language”. Right now, PHP is average at everything.
From where I stand, PHP has been a part of enterprise The release of PHP 8 began filling up log files with
programming for over two decades. Ten years ago, it was notices and warnings. With tons of new error log entries
tough to find a corporation that wasn’t using PHP somewhere. for mismatched types, undeclared variables, and undefined
From dynamic websites and discussion boards to APIs and array keys, upgrading to PHP 8 is no easy task. How many of
custom database integrations, PHP was everywhere. us even have complete control over every single variable we
If nothing else, WordPress is PHP and has powered a stag- need to use? I’m a consultant who has dealt with hundreds
gering percentage of the internet, which has continued to of projects in all industry levels, and I don’t know of a single
grow every year. Every year, that is, until 2023. WordPress project that has control over every array key in their system.
still has an impressive market share, and maybe the last two Third-party libraries and APIs are too prevalent.
years are just a temporary blip in the trajectory, but anecdot- This is leading to discussions about whether or not it is
ally, the drop in usage is concerning. Talking with customers worth upgrading to PHP 8 or if now is the time to switch to
and organizations, WordPress has become too buggy and too another language. It used to be easy to argue against the “new
difficult to manage. The shift to a JavaScript focus in Word- hot” trend because PHP had clear, unique strengths, but this
Press core has complicated plugin development. The shift to is no longer true. The argument of “It has the best commu-
admin panel/database-driven themes has made migrations nity!” is completely true, but it doesn’t sway project managers
and theme deployment increasingly difficult. More and more as much as I would like it to.
customers are hearing from marketing firms that they are Does this mean I’m finally jumping on the “PHP is dying;
restricted to what their designers can do through the Site time to switch to something else” bandwagon? Absolutely
Editor, and if they are going to be restricted to a GUI inter- not! But we, as a community, need to be careful because
face to build their website, they might as well use a hosted things are slipping. PHP is too wonderful, and we can’t give it
solution like Wix so they don’t have to mess with hosting the up without a fight.
site themselves.
Laravel has skyrocketed over the last decade becoming the Beth Tucker Long is a developer and owner
second most-used PHP framework after WordPress. It has at Treeline Design, a web development
an impressive adoption rate and a thriving marketplace of company, and runs Exploricon, a gaming
tools and libraries. LaraCons are selling out worldwide, even convention, along with her husband, Chris.
She leads the Madison Web Design & Devel-
while the conference market at large struggles to recover. But
opment and Full Stack Madison user groups.
Laravel usage has also been declining in the last few years.
You can find her on her blog (http://www.
alittleofboth.com) or on Twitter @e3BethT
1 https://phpa.me/terry-chay

You might also like