Creating Beautiful CLI Applications in PHP with MiniCLI and Termwind
Wendell Adriel
Usually, when we think about PHP programming, our minds automatically drift toward Web Applications. PHP is widely known for web-oriented solutions. However, it’s less recognized that PHP also has robust capabilities to build powerful CLI applications.
Command Line Interface (CLI) applications are essential
tools in the realm of software development. They provide
a user interface that interacts with a software or operating
system using commands. Unlike Graphical User Interface
(GUI) applications, where users interact with software using
graphical icons and visual indicators, CLI applications rely
solely on textual input and output.
Before PHP 4.3.0, creating CLI applications with PHP
wasn’t straightforward. It involved several workarounds and
resulted in applications that weren’t very efficient. However,
the introduction of a new CLI SAPI (Server Application
Programming Interface) from PHP 4.3.0 onwards revolutionized PHP usage in the CLI environment. This dedicated SAPI
is very similar to the one that PHP uses for web programming,
but it was engineered to perform exceptionally well in a CLI
setting.
PHP CLI applications can be scripts for automation, admin
tasks, tests, and even complex systems running in the background as daemons. They can also be trusted to manage file
system tasks, perform network communications, or handle
databases.
The beauty of CLI PHP applications is that they integrate
easily with the existing systems and standard workflows.
Whether you’re building a small script to automate mundane
tasks or complex systems, PHP for CLI applications provides
powerful tooling while being lightweight and efficient.
Indeed, PHP’s utility extends far beyond web application
development. Although much less discussed, PHP’s CLI
application development capabilities make it a highly versatile
programming language. While PHP is renowned for its stateless HTTP protocol for the web, it equally excels in managing
the stateful, interactive shell environment of CLI applications.
No matter what the use case, PHP’s broad array of built-in
functions and extensive library ecosystem ensure that PHP
developers are readily armed to tackle any project that comes
their way—including those working in a CLI context.
Creating and Running Our First CLI Script in PHP
PHP allows us to create simple CLI scripts, complex CLI
applications, and everything in between. But before we start
creating a full CLI application, let’s learn how we can create
and run simple CLI scripts with PHP. For the examples that
we are going to see in this article, we are going to use the latest
version of PHP, version 8.2.
Create a folder named hello-cli in your machine, and
inside this folder, create a file named hello.php:
mkdir hello-cli && cd hello-cli && touch hello.php
Now, open the hello.php file and add this content to it:
<?php
echo "Hello, CLI";
Save the file, go to the terminal, and run:
php hello.php
You are going to see the output Hello, CLI in your terminal.
Done. We created and ran our first CLI script in PHP, but
there are still some things we can do to make this even better.
As you can see, when running our script, we need to use the
PHP binary to execute it. What if we could execute it without
the need to explicitly use the PHP binary to do so? We can do
that, and I’m going to show how.
First, rename your hello.php file to
hello only (without
extension):
mv hello.php hello
Then open it, and update the content to this and save:
#!/usr/bin/env php
<?php
echo "Hello, CLI";
You can see that we just added a new line at the beginning
of the file: #!/usr/bin/env php. This line is responsible for
telling our OS that this file, which is now an executable one,
should use the PHP binary when executed.
Now try running it in your terminal:
./hello
You’re probably going to see an error message like this:
permission denied: ./hello
That’s because our file is not set to be executable. We need
to make a final adjustment setting the permissions on it to
make sure we can use it as an executable:
chmod +x ./hello
Now that our file is an executable, we can execute it as we
tried before:
./hello
This time, you will see the output Hello, CLI in your
terminal. Much better, right? We created a CLI script in PHP
as an executable file that we can run as any other executable
without the need to know that this was built with PHP. But
there are still some other details that we need to pay attention
to while creating CLI scripts and applications with PHP. Let’s
improve our script even more.
Open the script and update it to be like this: (See Listing 1)
You can see that we added two things to the script: an if
check at the beginning and wrapped the script logic in a try/
catch block. But why we did we make these changes?
Let’s understand the ifcheck first. The php_sapi_name function returns a lowercase string describing the type of interface
that PHP is using, so here we check if the context is not the
CLI—we won’t execute anything.
Now, the try/catch block is self-explaining. If the script
runs into any error, it will print the exception and return 1,
meaning that the process failed. When a CLI script runs and
You’ll see again the Hello, CLI as output. Now, let’s see how
the script behaves when an exception occurs. Update the
script to be like this: (See Listing 2)
Listing 2.
1. #!/usr/bin/env php
2. <?php
3.
4. if (php_sapi_name() !== 'cli') {
5. exit;
6. }
7.
8. try {
9. throw new Exception('DANGER - CLI ERROR!');
10. echo "Hello, CLI";
11. return 0;
12. } catch (Throwable $exception) {
13. echo "An error occurred\n";
14. echo $exception->getMessage();
15. return 1;
16. }
Now, go to the terminal and run once again our script:
./hello
You’ll see an output like this:
An error occurred
DANGER - CLI ERROR!
That’s great, right? Now we have a way to see the errors
from our script! But you’re probably thinking that our script
is missing something, and you’re right. We can do more than
just display data; we can also gather user input to make our
scripts more dynamic. So, let’s update our script to get the
user name and display a custom message! (See Listing 3)
Listing 1.
1. #!/usr/bin/env php
2. <?php
3.
4. if (php_sapi_name() !== 'cli') {
5. exit;
6. }
7.
8. try {
9. echo "Hello, CLI";
10. return 0;
11. } catch (Throwable $exception) {
12. echo "An error occurred\n";
13. echo $exception->getMessage();
14. return 1;
15. }
has a return different than 0, it means that the script failed.
That’s why the last line of the try block is returning 0, and the
last line of the catch block is returning 1.
Now, go to the terminal and run once again our script:
./hello
Listing 3.
1. #!/usr/bin/env php
2. <?php
3.
4. if (php_sapi_name() !== 'cli') {
5. exit;
6. }
7.
8. try {
9. $name = readline("What's your name?\n> ");
10. echo "Hello, {$name}\n";
11.
12. return 0;
13. } catch (Throwable $exception) {
14. echo "An error occurred\n";
15. echo $exception->getMessage();
16.
17. return 1;
18. }
To get user input in our CLI scripts
or applications, we can use the readline
function. It takes a string as an argument to be the prompt we show to the
user, and the user input is going to be
returned by it.
Now, save your script and run it again:
./hello
You’ll see an output like this:
What's your name?
>
Type your name and hit enter and
you’ll see an output like Hello, Wendell.
Beware that if the user hits enter without
giving any value, the readline function
will return false as a value instead of
a string. So like any other application,
make sure to validate the user input, but
for the sake of simplicity of this example,
we won’t add this validation.
And that’s it; we just built our first
PHP-powered CLI script! This is just
a really simple example of how you can
create CLI scripts with PHP. If you’re
not a huge fan of bash, you can start
creating your own scripts with PHP
and sharing them with your teammates
now!
PHP Libraries for CLI Applications
Symfony Console
The Symfony Console library is a
powerful tool that provides a robust
framework for building command-line
applications. This library, part of the
larger Symfony Framework, offers a lot
of flexibility and enhances PHP’s core
ability to create CLI applications. With
it, developers can define commands
and command parameters, handle user
inputs and outputs, leverage its built-in
testing capabilities, and do much
more in creating feature-rich CLI utilities. This library is also used by other
libraries as a base dependency, being
one of the most famous libraries for CLI
applications in PHP.
You can read more about it here:
https://phpa.me/symfony-comp-console[1].
Laravel Zero
Laravel Zero is an open-source,
streamlined version of Laravel that
is specifically designed for building
high-quality command-line applications. While Laravel is well-known for
its extensive applications for the web,
Laravel Zero targets the crafting of
lightweight yet powerful CLI applications. Built on top of the foundational
Laravel components, it adds a host
of convenient features dedicated to
enriching the CLI experience.
It provides features such as task
scheduling, automatic testing, and
Laravel’s extensive Eloquent ORM.
Laravel Zero also supports applica-
tion compiling into a PHAR archive,
making distribution easier.
It provides features such as task
scheduling, automatic testing, and
Laravel’s extensive Eloquent ORM.
Laravel Zero also supports applica-
tion compiling into a PHAR archive,
making distribution easier.
You can read more about it here:
https://laravel-zero.com[2].
Laravel Prompts
Laravel Prompts is a highly useful
library that can be extensively used
for the development of command-line
applications. It presents the functionality of a more interactive set of tools for
collecting user input in CLI applications
beyond the traditional commandline prompts. It has been designed to
enhance the Laravel artisan console
capabilities (but not limited to it, since
you can use it on other PHP projects),
and it intelligently extends the built-in
prompt methods to provide richer user
interaction.
Offering features that are common
in web applications to CLI apps like
selects, checkboxes, tables, progress
bars, placeholders and data validation,
the library aims to create beautiful and
rich CLI apps with ease.
1 https://phpa.me/symfony-comp-console
2 https://laravel-zero.com
You can read more about it here:
https://laravel.com/docs/10.x/prompts[3].
MiniCLI
The MiniCLI library is a micro-framework that offers developers a
minimalist and highly efficient toolset
to create command-line applications.
As it doesn’t require any dependencies,
MiniCLI makes for a perfect option
when building lightweight, standalone
CLI PHP applications. It provides a
slim, portable solution that is ideal for
projects that need a slim and highly
customizable CLI tool.
Despite its minimalist design, it
doesn’t compromise productivity. It
provides a structured way to create
and organize commands alongside a
variety of helpers to handle user input
and print colored output to the terminal
with support for themes.
You can read more about it here:
https://docs.minicli.dev[4].
Termwind
Termwind is a unique library tailored
for crafting beautiful command-line
applications. Heavily inspired by Tail-
wind CSS, a utility-first CSS framework,
Termwind brings a similarly structured
approach to the CLI world. With Termwind, printing styled text to the console
becomes a breeze through its stylish,
fluent API. The library provides a means
to control and format console text
output with the same level of nuance
and freedom traditionally found in web
development.
Termwind extends PHP’s existing
output capabilities in a way that is both
familiar to Tailwind users and intuitive
to developers new to the framework. It
offers the ability to control text colors,
background colors, formatting options,
and alignment. You can add borders
to elements, control spacing, and even
handle inline elements—essentially, it
brings styles to your terminal!
3 https://laravel.com/docs/10.x/prompts
4 https://docs.minicli.dev
You can read more about it here: https://github.com/
nunomaduro/termwind[5].
Now that we already created our first CLI script with PHP
and checked some libraries used to build CLI applications,
let’s create our first full CLI application! For this, we will
use two of the libraries described in the previous section:
MiniCLI and Termwind.
MiniCLI is my go-to library when building CLI appli-
cations in PHP because it’s a lightweight yet powerful
micro-framework with many features to build fast, small, and
amazing CLI applications. Combining it with Termwind is a
spicy combination because we can easily create beautiful and
rich CLI interfaces with Termwind.
We are going to create a small but useful helper application
that can resize and convert images called CLImage. For that,
we will use an official template from MiniCLI that already
supports Termwind and Plates (a minimalist and lightweight
template engine for PHP) called MiniTerm: https://github.
com/minicli/miniterm[6].
Miniterm Overview
Let’s start by creating our project:
composer create-project minicli/miniterm CLImage &&
cd CLImage
Before starting to code our application, let’s take a look at
the project structure and the demo commands to understand
how MiniCLI and Termwind work together.
The project has four main folders:
-
app - This is where your application code lives. Here,
you’ll find four subfolders:- Command - All your application
commands
-
Config - These are configuration classes that can change the
behavior of your application. This template already has two
configuration files that set how Termwind is used.
-
Services - These are services that you can create to host
business logic or to configure 3rd party libraries into your
application. This template already has two services: one for
Termwind and another one for Plates.
-
Views - All view files that you can use the Plates engine with
Termwind to create beautiful outputs for your application.
-
config - This is where all configuration files for your
application live. This template already comes with three
configuration files, but you can create additional configu-
ration files and access the values from it in your application
with a config helper as well as create additional properties
inside the existing configuration files (similar to what we
have in Laravel).- app.php - This is the main configuration file
5 https://github.com/nunomaduro/termwind
6 https://github.com/minicli/miniterm
for the application, where you can set the application name,
the path(s) for the commands, and theme and disable/enable
the debug mode.
-
logging.php - This is where you can configure how the logs
will work for your application. You can define the type (single
log file or daily log file), the default log level and the timestamp format for the logs.
-
services.php - This is where you can configure all the
services for your application. Those services are going to be
injected into the application container so you can use them in
your application through their names (keys).
-
logs - This is where all your log files are going to be created.
-
tests - This is where live all the application test files.
The entry point for our application is the minicli file at the
root of the project. When you open this file, you’ll see that
it has a lot of things in common with the script we created
earlier in this article. Go to the terminal and run the application:
./minicli
You’re going to see an output like this:
MiniTerm - MiniCLI Application Template powered with
Termwind and Plates
If you pay attention, that’s printing what we have defined
in the app_name property of the
config/app.php file. So, the
first thing we will do is update this. Update this property to
CLImage---Helper Tool for Images. Save the changes, and let’s
also rename our executable file:
mv ./minicli CLImage
Now run:
./CLImage
You’re going to see an output like this:
CLImage - Helper Tool for Images
###### Understanding Minicli Commands
Now that we have set our application name and renamed
the executable file, let’s take a look at some of the demo
commands that come with the template. Run this command:
./CLImage demo
You are going to see an output like this: (See Figure 1)
You are going to see an output like this: (See Figure 1)
Figure 1.
Let’s understand how this command is executed. MiniCLI
uses a file-based “routing” to define the commands. You can
see that inside the app/Command folder, we have a folder named
Demo. For MiniCLI, this is how you define a command name.
You can also see that inside the Demo folder, we have multiple
files. That’s how we define sub-commands with MiniCLI.
Among the files inside this folder, we have a file named
DefaultController.php. When you run a command without
any other sub-commands, this is the class responsible for the
root command. So when we ran ./CLImage demo, the class
being executed was the DefaultController. Let’s check it: (See
Listing 4)
This is a pretty straightforward command. It extends the
BaseController from the template that extends the base
controller from the MiniCLI library itself and adds some
methods to use Termwind and Plates, like the render method.
As you can see here, the render method accepts an HTML
string with some Tailwind CSS classes; you can check which
ones are supported in the Termwind docs to style the output
we display in the terminal.
Using Command Parameters
Now, let’s take a look at another example that uses command
parameters. Run this:
./CLImage demo test
You’ll see an output like this: (See Figure 2)
Figure 2.
Now run the same command, but passing an user param:
./CLImage demo test user=YOUR_NAME
You’ll see an output like this: (See Figure 3)
Figure 3.
You can see that the command is using the user param to
customize the message, so let’s see how that’s done. As you can
Listing 4.
1. <?php
2.
3. declare(strict_types=1);
4.
5. namespace App\Command\Demo;
6.
7. use App\Command\BaseController;
8.
9. class DefaultController extends BaseController
10. {
11. public function handle(): void
12. {
13. $this->render(<<<HTML
14. <div class="py-2">
15. <div class="px-1 bg-cyan-600">INFO</div>
16. <span class="ml-1">
17. Run <span class="font-bold italic">
18. ./minicli help
19. </span> for usage help.
20. </span>
21. </div>
22. HTML);
23. }
24. }
imagine, since we are using a sub-command called test from
the demo
command, the class responsible for that sub-com-
mand is the TestController inside the Demo folder. (See Listing
5 on the next page)
Checking the code above you can see that the only thing
different that we have here is the call to two methods: hasParam
and getParam. These are methods that MiniCLI provides for
checking and getting input data directly from the command
calls.
Now that we know how to handle user input from the
command call, let’s see another example to learn how to ask
for user input when running commands. Run this command:
./CLImage demo ask
You’ll see an output like this: (See Figure 4)
Figure 4.
You’ll see an output like this: (See Figure 3)
Figure 3.
Listing 5.
1. <?php
2.
3. declare(strict_types=1);
4.
5. namespace App\Command\Demo;
6.
7. use App\Command\BaseController;
8.
9. class TestController extends BaseController
10. {
11. public function handle(): void
12. {
13. $name = $this->hasParam('user') ?
14. $this->getParam('user') : 'World';
15.
16. $this->render(<<<HTML
17. <div class="py-2">
18. <div class="px-1 bg-green-600">MiniTerm</div>
19. <em class="ml-1">
20. Hello, {$name}
21. </em>
22. </div>
23. HTML);
24. }
25. }
Type your name and press enter. You’ll see an output like
this: (See Figure 5)
Figure 5.
Now, you can see that instead of getting user input on
the command call, we are getting the user input during the
command execution. Let’s see how we can do that. Open the
AskController inside the Demo folder. (See Listing 6)
In the code above, you can see that we have a call to a
new ask method. This method is really similar to the render
method; the only difference is that it waits for user input and
stores it into a variable.
Creating Views with Plates
We already know how to handle user input during the
command execution. But we are missing one thing. We saw
that this template uses the Plates template engine, but we
didn’t see any examples of how to use it. So let’s check one
more command that comes with MiniTerm:
./CLImage demo table
You’ll see an output like this: (See Figure 6)
Figure 6.
Let’s check how this table is created in the TableController
inside the Demo folder. (See Listing 7 on the next page)
Listing 6.
1. <?php
2.
3. declare(strict_types=1);
4.
5. namespace App\Command\Demo;
6.
7. use App\Command\BaseController;
8.
9. class AskController extends BaseController
10. {
11. public function handle(): void
12. {
13. $name = $this->ask(<<<HTML
14. <span class="mt-1 ml-2 mr-1
15. bg-green px-1 text-black">
16. What is your name?
17. </span>
18. HTML);
19.
20. $this->render(<<<HTML
21. <div class="py-2">
22. <div class="px-1 bg-green-600">MiniTerm</div>
23. <em class="ml-1">
24. Hello, {$name}
25. </em>
26. </div>
27. HTML);
28. }
29. }
You’ll see an output like this: (See Figure 6)
Figure 6.
Listing 7.
1. <?php
2.
3. declare(strict_types=1);
4.
5. namespace App\Command\Demo;
6.
7. use App\Command\BaseController;
8.
9. class TableController extends BaseController
10. {
11. public function handle(): void
12. {
13. $headers = ['Header1', 'Header2', 'Header3'];
14. $rows = [];
15.
16. for ($i = 1; $i <= 10; $i++) {
17. $rows[] = [
18. (string) $i,
19. (string) rand(0, 10),
20. "other string {$i}",
21. ];
22. }
23.
24. $this->view('table', [
25. 'headers' => $headers,
26. 'rows' => $rows
27. ]);
28. }
29. }
In the code above, you can see that we are just creating
some random values for a table, and then we are calling a view
method that accepts two parameters: the first one is the view
filename, and the second is an array with the data that we
want to pass to this view. As we saw earlier, all the views are
stored in the app/Views folder. Open the table.php file inside
it: (See Listing 8)
As you can see, this is a really simple HTML table, but
you can create complex views using any classes supported
by Termwind to style the output of your commands. If this
syntax is unfamiliar, check out the Plates template engine
docs here: https://platesphp.com[7].
Creating Our Service
Now that we have seen how MiniCLI and Termwind work
and how the MiniTerm template is structured, let’s start
creating our CLImage application. The first thing that we
are going to create is a service that’s going to be responsible
for image handling. For our application, we will rely upon
the Imagick PHP extension, so if you don’t have it installed,
check the docs on how to install it: https://phpa.me/symfony[8].
If you have pecl installed, it’s as easy as running:
pecl install imagick
7 https://platesphp.com
8 https://phpa.me/symfony
Listing 8.
1. <table>
2. <thead>
3. <tr>
4. <?php foreach ($headers as $header): ?>
5. <th><?=$this->e($header) ?></th>
6. <?php endforeach; ?>
7. </tr>
8. </thead>
9. <tbody>
10. <?php foreach ($rows as $row): ?>
11. <tr>
12. <?php foreach ($row as $cell): ?>
13. <td><?=$this->e($cell) ?></td>
14. <?php endforeach; ?>
15. </tr>
16. <?php endforeach; ?>
17. </tbody>
18. </table>
Let’s create our service. Go to the root of the project and
run:
touch app/Services/ImageService.php
Update it to be like this:
<?php declare(strict_types=1);
namespace App\Services;
use Minicli\App;
use Minicli\ServiceInterface;
final class ImageService implements ServiceInterface
{
public function load(App $app): void
{
// Nothing to do here
}
}
In the code above, we create a skeleton of our service. All
services must implement the Minicli\ServiceInterface and
define a load method. This method is needed if you need
to set anything for your service to correctly function in the
application when it’s loaded into the container. Our Image-
Service won’t need anything, so we can leave it empty. Now,
let’s register our service in our application. Open the config/
services.php file and add our service there like this:
<?php declare(strict_types=1);
use App\Services\ImageService;
use App\Services\PlatesService;
use App\Services\TermwindService;
return [
/*************************************************
* Application Services
* ---------------------------------------------- *
* The services to be loaded for your application.
************************************************/
'services' => [
'termwind' => TermwindService::class,
'plates' => PlatesService::class,
'image' => ImageService::class,
],
];
With our service registered, we can call any public method
of the service like this in our commands: $this->app->image```
METHOD.
Our application will have two commands: one to **resize**
images and another to **convert images. So, let’s update our**
service to handle these. Open the ImageService class that we
created and update it to be like this: (See Listing 9)
Let’s understand our service. It has three public methods:
- `info - Used to get information from an image. We are`
going to use this to print information on the images in
the terminal.
- `resize - Used to create a copy of an image with the`
given width and height.
- `convert - Used to create a copy of an image with the`
given format.
The methods are really simple and we have some common
helper methods and classes that we are using to make the
code cleaner and reusable.
We have a `read method that’s responsible for creating`
an `Imagick object from the image path that we are going to`
provide. The Imagick object is provided by the imagick extension, and it will allow us to resize and convert images without
the need for any additional PHP library.
All three public methods from this service also use an
ImageInfo class, which is just a simple DTO that we will use to
wrap information about an image. Let’s create this class. First,
create a Support folder inside the app folder and create the file
inside it:
mkdir app/Support && touch app/Support/ImageInfo.php
Then update this file to be like this:
$item->value, self::cases()
);
}
}
```
You can see that we are creating a `BackedEnum with the`
formats that we will support, in this case, just JPEG and PNG
for this example. But Enums are great for not only describing
values but also putting some logic that is related to these
values. In this case, we have a static function, allowedFormats,
that’s going to return an array with the values of all the cases
listed in our `Enum, which is going to be very handy in our`
application.
###### The Resize Command
Now that we have our ImageService complete, we can start
working on the command that’s going to be used for resizing
images. As we saw earlier in this article, MiniCLI uses a file**based convention for naming the commands. So let’s create**
a folder named Resize inside the app/Command folder, and then
inside this folder, let’s create a class named DefaultController
since the command is not going to have sub-commands:
```
mkdir app/Command/Resize &&
touch app/Command/Resize/DefaultController.php
```
-----
Now open the newly created DefaultController and update
it to be like this: (See Listing 10 near end of the article)
The command is not complex; we are just getting some input
from the user using the ask method that we saw earlier, doing
some validations on the input given by the user, and using
the `ImageService through the` `app instance:` `$this->app->im-`
```
age->resize to resize the image and print some information
```
about the process to the user.
Now you can see that we have two **Custom** **Exceptions:**
```
FileNotFoundException and InvalidDimensionException that
```
we are using. **Custom** **Exceptions are the best and recom-**
mended way of handling errors on MiniCLI because it’s going
to use these exceptions to print messages to the user if something goes wrong.
Let’s create those. First, create a folder called `Exceptions`
inside the app folder, and then let’s create these two exception
classes inside it:
```
mkdir app/Exceptions &&
touch app/Exceptions/FileNotFoundException.php &&
touch app/Exceptions/InvalidDimensionException.php
```
These custom exceptions are going to be really simple; we
are just going to extend the base `Exception class from PHP`
and add a custom message to them.
Let’s start with the FileNotFoundException. Update it to be
like this:
```
app->config->output_path.
Here, we are accessing the application configuration that we
define in our configuration files inside the config folder. Open
the config/app.php file and add this new output_path configuration:
```
'CLImage - Helper Tool for Images',
'app_path' => [
__DIR__.'/../app/Command',
'@minicli/command-help'
],
'theme' => '',
'debug' => true,
'output_path' => __DIR__.'/../output',
];
```
Here we are setting the output path to be a folder named
```
output at the root of the project. We don’t have this folder yet,
```
so let’s create it:
```
mkdir output
```
Now, with our resize command finished, let’s test it! Go to
the terminal and run:
```
./CLImage resize
```
You’ll see an output like this: (See Figure 7)
You’ll see an output like this: (See Figure 7)
Figure 7.
-----
see an output like this: (See Figure 8)
Figure 8.
You can see that this is the error message defined in our
`FileNotFoundException. That’s why you should use` **Custom**
**Exceptions to provide meaningful error messages for your**
users.
Now, run the command again, but provide an existing file,
and you’ll see an output like this: (See Figure 9)
Figure 9.
You can see that now the information about the selected
file is displayed in a table, and now we have another question
about what’s the new width that we want for the image. For
our example, the width and height can’t be less than 10 **or**
**greater than the original dimension of the image. To see our**
```
InvalidDimensionException in action, type 5 and press enter.
```
You’ll see an output like this: (See Figure 10)
Figure 10.
As you can see, the error message we defined in our Custom
**Exception class is displayed. Now, run the command again**
and provide the same image, such as 1024, as the new width
and height. You’ll see an output like this: (See Figure 11)
As you can see, it displays a success message and prints the
information about the resized image. If you check the output
folder now, you can see that the new file is there.
###### The Convert Command
Now, let’s work on the command that’s going to be used
for converting images. Create a folder named Convert inside
the app/Command folder, and then inside this folder, let’s create
a class named `DefaultController since the command is not`
going to have sub-commands:
```
mkdir app/Command/Convert &&
touch app/Command/Convert/DefaultController.php
```
Now open the newly created DefaultController and update
it to be like this: (See Listing 12 at end of article)
You can see that this command is simpler than the resize
one, and we are using the same helper methods we created
for the resize command here as well.
We do have some differences; we are using the allowedFor```
mats method from the ImageFormat enum that we created to
```
validate if the format given by the user is valid, and we are
using a new **Custom Exception for the validation:** `Invalid-`
```
FormatException. Let’s create this new exception at app/
```
Exceptions/InvalidFormatException.php with these contents.
```
read($imagePath);
22.
23. return new ImageInfo(
24. filename: basename($imagePath),
25. width: $image->getImageWidth(),
26. height: $image->getImageHeight(),
27. );
28. }
29.
30. /**
31. * @throws ImagickException
32. */
33. public function resize(
34. string $imagePath,
35. int $width,
36. int $height,
37. string $outputPath
38. ): ImageInfo {
39. $image = $this->read($imagePath);
40.
41. $image->resizeImage(
42. columns: $width,
43. rows: $height,
44. filter: Imagick::FILTER_UNDEFINED,
45. blur: 1,
46. bestfit: true
47. );
48.
49. $filename = basename($imagePath);
50. $output = "{$outputPath}/{$filename}";
51. $image->writeImage($output);
52.
53. return new ImageInfo(
54. filename: realpath($output),
55. width: $width,
56. height: $height,
57. );
58. }
59.
```
Listing 9 continued.
```
60. /**
61. * @throws ImagickException
62. */
63. public function convert(
64. string $imagePath,
65. ImageFormat $format,
66. string $outputPath
67. ): ImageInfo {
68. $image = $this->read($imagePath);
69.
70. $image->setImageFormat($format->value);
71.
72. $filename = pathinfo(
73. $imagePath,
74. PATHINFO_FILENAME
75. ) . ".{$format->value}";
76. $output = "{$outputPath}/{$filename}";
77. $image->writeImage($output);
78.
79. return new ImageInfo(
80. filename: realpath($output),
81. width: $image->getImageWidth(),
82. height: $image->getImageHeight(),
83. );
84. }
85.
86. /**
87. * @throws ImagickException
88. */
89. private function read(string $imagePath): Imagick
90. {
91. $image = new Imagick();
92. $image->readImage($imagePath);
93.
94. return $image;
95. }
96.
97. public function load(App $app): void
98. {
99. // Nothing to do here
100. }
101. }
```
-----
Listing 10.
```
1. buildQuestion($question);
13. $imagePath = $this->ask($askQuestion);
14.
15. if ( ! realpath($imagePath)) {
16. throw new FileNotFoundException();
17. }
18.
19. $imageInfo = $this->app->image->info($imagePath);
20. $this->printImageInfo($imageInfo);
21.
22. $newW = $this->ask(
23. $this->buildQuestion(
24. "What is the new WIDTH? " .
25. "[10..$imageInfo->width]"
26. )
27. );
28.
29. if ($newW < 10 || $newW > $imageInfo->width) {
30. throw new InvalidDimensionException(
31. $imageInfo->width
32. );
33. }
34.
35. $newH = $this->ask(
36. $this->buildQuestion(
37. "What is the new HEIGHT?" .
38. "[10..{$imageInfo->height}]"
39. )
40. );
41.
42. if ($newH < 10 || $newH > $imageInfo->height) {
43. throw new InvalidDimensionException(
44. $imageInfo->height
45. );
46. }
47.
48. $result = $this->app->image->resize(
49. $imagePath,
50. (int) $newW,
51. (int) $newH,
52. $this->app->config->output_path
53. );
54.
55. $this->successMessage('Resized successfully!' .
56. 'Check the details below:');
57. $this->printImageInfo($result);
58. }
59. }
```
Listing 12.
```
1. ask(
21. $this->buildQuestion(
22. 'Which image do you want to convert?'
23. )
24. );
25.
26. if ( ! realpath($imagePath)) {
27. throw new FileNotFoundException();
28. }
29.
30. $imageInfo=$this->app->image->info($imagePath);
31. $this->printImageInfo($imageInfo);
32.
33. $allowedFormats=ImageFormat::allowedFormats();
34. $newFormat = $this->ask(
35. $this->buildQuestion(
36. 'What is the new FORMAT? ['.
37. implode(', ', $allowedFormats).
38. ']'
39. )
40. );
41.
42. if ( ! in_array($newFormat, $allowedFormats)) {
43. throw new InvalidFormatException();
44. }
45.
46. $result = $this->app->image->convert(
47. $imagePath,
48. ImageFormat::from($newFormat),
49. $this->app->config->output_path
50. );
51.
52. $this->successMessage('Success!' .
53. 'Check the details below:');
54. $this->printImageInfo($result);
55. }
56. }
```
-----
Listing 11.
```
1.
20.
CLImage
21.
22. {$question}
23.
24.
25. HTML;
26. }
27.
28. protected function successMessage(
29. string $message
30. ): void {
31. $this->render(<<
33.
SUCCESS
34.
35. {$message}
36.
37.
38. HTML);
39. }
40.
41. protected function printImageInfo(
42. ImageInfo $imageInfo
43. ): void {
44. $this->view('table', [
45. 'headers' => ['FILENAME', 'WIDTH', 'HEIGHT'],
46. 'rows' => [
47. [$imageInfo->filename,
48. "{$imageInfo->width} px",
49. "{$imageInfo->height} px"]
50. ],
51. ]);
52. }
53.
```
Listing 11 continued.
```
54. protected function render(
55. string $content,
56. int $options = OutputInterface::OUTPUT_NORMAL
57. ): void {
58. $this->getApp()->termwind
->render($content, $options);
59. }
60.
61. protected function style(
62. string $name,
63. Closure $callback = null
64. ): Style {
65. return $this->getApp()
66. ->termwind
67. ->style($name, $callback);
68. }
69.
70. protected function terminal(): Terminal
71. {
72. return $this->getApp()->termwind->terminal();
73. }
74.
75. protected function ask(
76. string $question,
77. iterable $autocomplete = null
78. ): mixed {
79. return $this->getApp()
80. ->termwind
81. ->ask($question, $autocomplete);
82. }
83.
84. protected function view(
85. string $template,
86. array $data = []
87. ): void {
88. $this->getApp();
89. ->termwind
90. ->render(
91. $app->plates->view($template, $data)
92. );
93. }
94. }
```
-----
### Bounded Fix
###### Edward Barnard
For the past four years at the USA Clay Target League, we’ve been working toward updating our PHP software. Domain-Driven Design appeared to be a good direction for us. We’re not there yet. But there is one particular pattern that I’ve been using over and over, which I’ll be sharing below.
_3 Evans, Eric. Domain-Driven Design:_
_Tackling Complexity in the Heart of_
_Software. Boston: Addison-Wesley, 2004,_
_page 365._
_tangled, and even when they are_
_orderly they are usually not well_
_suited to DDD… this first strategy_
_does not require a big commitment_
_to DDD. It allows even a small_
_team to achieve a modest objective_
_involving some intricate domain_
_logic and, ideally, one with some_
_strategic value. Then at some point_
_the bubble bursts. The carefully_
_designed code is gradually reab-_
_sorbed by the legacy. It is no longer_
_a platform for innovative new_
_development._
Our software is in transition as
we evolve to a different framework,
different ORM, an API-First architecture, and so on. As with many PHP
codebases that have been under development for ten years or more, it’s often
hard to fix or change something without
risk. I could fix a database query, for
example, and break something else
because that something else relies on
that same database query.
To me, this seems obvious – that
introducing change carries the risk of
also introducing an unexpected side
effect (breaking something). We’ve had
a process problem in that we’ve not
sufficiently recognized how fragile our
codebase really is. We’re getting better
at explicitly recognizing this risk with
each new proposed change.
I now see more and more tasks
phrased like this:
- Make this change with minimum risk of breaking any existing
production features or
- Fix this broken Model (database
query) without breaking anything
else that might also be using that
Model/query or
- Introduce this feature without
creating a cross-dependency to
other existing features.
At the same time, we want the
assigned task to take us in the direction
we’re trying to evolve.
Given the strong desire to not break
anything while quickly accomplishing
the task at hand, and also given the fact
that we have no automated regression
tests to help catch breakages, I’ve been
using a “tiny” version of a Bounded
Context. Essentially, I’m encapsulating
the new feature/fix/tool inside a new
folder, doing my best to avoid any
outside dependencies.
###### Seam Model and Bubble Context
This idea has been around for a while.
Michael Feathers, in Working Effectively
_With Legacy Code[1], presents “The Seam_
Model” (Chapter 4):
_A seam is a place where you can_
_alter behavior in your program_
_without editing in that place…_
_When you need to add a feature to_
_a system and it can be formulated_
_completely as new code, write the_
_code in a new method. Call it from_
_the places where the new function-_
_ality needs to be. You might not be_
_able to get those call points under_
_test easily, but at the very least, you_
_can write tests for the new code._
Eric Evans, in “Getting Started with
DDD When Surrounded by Legacy
Systems[2]”, describes the situation with
“Strategy 1: Bubble Context”:
_We say that effectively applying the_
_tactical techniques of DDD requires_
_a clean, bounded context. This can_
_be a daunting requirement when_
_your work is dominated by legacy_
_systems. These systems are often_
_1 Feathers, Michael C. Working Effectively_
_with Legacy Code. Robert C. Martin_
_Series. Upper Saddle River, N.J: Prentice_
_Hall, 2013, pages 31 and 59._
_2 “DDD Surrounded by Legacy Soft-_
_ware.” White paper copyright_
_2013. Accessed November 5, 2023._
_[https://phpa.me/ddd-surrounded](https://phpa.me/ddd-surrounded)_
Evans defines his Bubble Context as:
_A “bubble” is a small bounded_
_context established using an Anti-_
_corruption Layer for the purpose_
_of a particular development effort_
_and not intended necessarily to be_
_expanded or used for a long time._
Every answer brings another question! What’s an Anticorruption Layer[3]?
_We need to provide a translation_
_between the parts that adhere to_
_different [conceptual] models, so_
_that the models are not corrupt-_
_ed with undigested elements of_
_foreign models. Therefore, create_
_an isolating layer to provide clients_
_with functionality in terms of their_
_own domain model. The layer talks_
_to the other system through its_
_existing interface, requiring little or_
-----
###### Bounded Fix
_no modification to the other system. Internally, the layer_
_translates in both directions as necessary between the two_
_models._
With our legacy codebase, when we disentangle pieces of
code, we’re essentially placing the extracted piece of code in
its own separate world so that it can play by its own rules with
its own assumptions. The separation is what Evans calls the
Anticorruption Layer. For example, we’ve also been evolving
our database tables. New features reference the new table,
while old features reference a MySQL View constructed to
look like the old table. That MySQL View is an Anticorruption Layer protecting new-table queries from being polluted
by the needs/assumptions of the old-table queries.
Why do we care about these terms and definitions? With
any design pattern, I find the theory to be as important as the
suggested or sample code. I need to know why I am taking
this approach so that I can think about how best to design the
software I’m writing.
Below, we’ll see a simple example of hiding a small feature,
fix, or tool inside its own folder. With the new folder (and,
therefore, new/separate PHP namespace), we minimize the
risk of breaking anything else along the way. We’re creating
a vanishingly small bounded context. I call it a “bounded fix”
or “feature in a pocket” to convey the idea that it’s purposely
small and self-contained.
###### One-off Project
We’ve been maintaining hand-edited KML (Keyhole
Markup Language) files identifying the latitude/longitude
of each of our participating teams. (The name “Keyhole”
appears to derive from the original 1970s military reconnaissance satellites that were essentially peeking through
keyholes.) I wrote a one-off project to import those files into
a new MySQL table. Looking at one of the KML files showed
me that this was a straightforward XML import. “Old school”
PHP core features would be sufficient. However, I also know
that “one-off” projects are sometimes not as “one-off” as
planned. There’s value in hanging on to the code, either for a
second later import, or for answering questions as to precisely
how the import was done, or to use as an example for some
Listing 2.
```
1. setView(__DIR__ . '/View');
14. return new Kml($view, self::repository());
15. }
16.
17. private static function repository(): RKml
18. {
19. return new RKml(new HandCodedWrite());
20. }
21. }
```
other project in the future. I already had a folder It_Tools/ for
this sort of thing, so we’ll use that as our starting point.
###### Page Controller
Listing 1 shows the simple page controller for this one-off
tool. The feature itself is the $controller object. I call it the
“controller” here because it’s the thing that orchestrates the
feature processing, like the Controller of the Model-ViewController (MVC) design pattern.
In this case, the feature’s name is “Kml”, and I instantiate the
feature with KmlFactory::create(). This is the starting point
of my “bounded fix” pattern: for feature XXX, instantiate it
with XXXFactory::create(). The business process is simple.
When I click the “import” button (POST method), run the
tool via `$controller->execute(). Either way, display results`
on the webpage via $controller->render().
###### Factory
Listing 2 shows the Factory class responsible for assembling
the feature/fix/tool into a class returned by create().
Listing 1.
```
1. execute();
11. }
12. echo $controller->render();
```
This separate factory class is arguably over-engineering
the result. However, I find it works out well as a consistent
approach. I’ve been making quite a number of these bounded
fixes, features, or tools, as we disentangle our code. When I
see XXXFactory::create(), I know what results to expect.
Also, including the Factory from the start allows for modest
expansion or extension. I can add to the feature’s capabilities
“under the covers” without changing how I access the feature
in the first place.
We’re currently migrating to Twig but not Symfony. Since I
want this tool to be self-contained, I want the twig templates
-----
###### Bounded Fix
to be in the same folder (and thus separate from everything **Controller/service**
else). The call setView(__DIR__ . '/View') accomplishes this.
Listing 3 shows the main service (which I call $controller
Listing 3. above). Since this is a one-off project, I simply placed the
KML files (to import) inside the same folder structure. The
`1. view = $view; 6. {
20. $this->repository = $repository; 7. public const SQL_INSERT_PLACEMARKS = <<< SIP
21. } 8. insert ignore into `placemarks` (`league_id`, `doc_name`,
22. 9. `point_name`, `coord_x`, `coord_y`, `coord_z`)
23. /** 10. VALUES (?, ?, ?, ?, ?, ?)
24. * Where to find the KML files to import 11. SIP;
25. * 12.
26. * @return string 13. }
27. */
28. public function getDataDir(): string Listing 5.
29. {
30. return realpath(__DIR__ . '/Data/Maps'); 1. CREATE TABLE `placemarks` (
31. } 2. `id` int unsigned NOT NULL AUTO_INCREMENT,
32. 3. `league_id` int unsigned NOT NULL,
33. public function listFiles(): array 4. `doc_name` varchar(255) NOT NULL,
34. { 5. `point_name` varchar(255) NOT NULL,
35. return glob($this->getDataDir() . '/*.kml'); 6. `coord_x` varchar(255) NOT NULL,
36. } 7. `coord_y` varchar(255) NOT NULL,
37. 8. `coord_z` varchar(255) NOT NULL,
38. public function execute(): void 9. PRIMARY KEY (`id`),
39. { 10. UNIQUE KEY `doc_name` (`doc_name`,`point_name`),
40. foreach ($this->listFiles() as $file) { 11. KEY `league_id` (`league_id`),
41. $this->repository->importKml($file); 12. CONSTRAINT FOREIGN KEY (`league_id`)
42. } 13. REFERENCES `leagues` (`id`)
43. } 14. ) ENGINE=InnoDB;
44.
45. public function render(): string
46. {
```
`47.` `$self` `=` `$_SERVER['PHP_SELF'];` **File Import**
```
48. $files = $this->listFiles();
```
The repository (Listing 6 on the next page) imports one file
```
49. $dataDir = $this->getDataDir();
```
at a time. My personal preference is to use hand-coded SQL
```
50. $twig = compact('self', 'files', 'dataDir');
```
`51.` `try {` for situations like this, and since I wrote the code, that’s how
`52.` `return` `$this->view->render('kml_html.twig',` `$twig);` it happened! The “old school” `SimpleXMLElement class works`
`53. } catch (LoaderError|RuntimeError|SyntaxError $e) {` fine for ingesting and traversing the KML file.
```
54. return $e->getMessage();
55. }
56. }
57. }
```
-----
###### Bounded Fix
Listing 6.
```
1.
2. declare(strict_types=1);
3.
4. namespace Subsystems\IT_Tools\Populate\KML_Import;
5.
6. use LegacyBoundedContexts\Infrastructure\WrapDBAL\
DomainModel\Interfaces\IHandCodedWrite;
7. use SimpleXMLElement;
8.
9. use function explode;
10. use function file_get_contents;
11. use function preg_replace;
12. use function str_replace;
13. use function trim;
14.
15. class RKml implements SQLKml
16. {
17. private IHandCodedWrite $write;
18.
19. public function __construct(IHandCodedWrite $write)
20. {
21. $this->write = $write;
22. }
23.
24. public function importKml(string $path): void
25. {
26. $sql = self::SQL_INSERT_PLACEMARKS;
27. $path = preg_replace('|^.+/|', '', $path)
28. $leagueId = (int)str_replace('.kml', '', $path);
29. $xml = new SimpleXMLElement(file_get_contents($path));
30. $docName = trim((string)$xml->Document->name);
31. $placemarks = $xml->Document->Folder->Placemark;
32. foreach ($placemarks as $placemark) {
33. $pointName = trim((string)$placemark->name);
34. $coords = trim($placemark->Point->coordinates);
35. [$coordX, $coordY, $coordZ] = explode(',', $coords);
36. $parms = [$leagueId, $docName, $pointName,
37. $coordX, $coordY, $coordZ,];
38. $this->write->updateRow($sql, $parms);
39. }
40. }
41. }
```
###### Summary
I find that, more and more often, I’m creating a small,
self-contained fix, feature, or tool. I’m doing this to help
ensure that, when introducing new code, I don’t risk breaking
anything else in the process.
This risk is especially pronounced with any feature that
touches the database. That’s because our Model classes are far
more interconnected (and fragile) than they should be. When
fixing a database query, it’s far safer to write a completely new
query and abandon using the current query.
In essence, we’re creating transitional code. It’s a step in the
direction of our target architecture without being the _final_
step in that direction. This “temporary” code could remain
in place for several years, to be sure, but if we introduced the
desired change without breaking anything else, it’s a success.
###### Related Reading
- _DDD Alley: Create Observability, Part 3: Rewrite Busi-_
_ness Process by Edward Barnard, August 2023._
[https://www.phparch.com/article/2023-08-ddd/](https://www.phparch.com/article/2023-08-ddd/)
- _DDD Alley: Create Observability, Part 4: Simple Queue_
_System by Edward Barnard, September 2023._
[https://www.phparch.com/article/2023-09-ddd/](https://www.phparch.com/article/2023-09-ddd/)
- _DDD Alley: Create Observability, Part 5: Offline_
_Processes by Edward Barnard, October 2023._
[https://www.phparch.com/article/2023-10-ddd/](https://www.phparch.com/article/2023-10-ddd/)
_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._
-----
### Alternative Architecture in Laravel
###### Steve McDougall
In Laravel, we are used to writing our code exactly as it comes out of the box. Model View Controller (MVC) pattern is something we are extremely familiar with—and the documentation leads us in this direction by default. However, does this work in every scenario? What happens when features pile up or our team grows to a point that makes it harder to manage and not trip over each other? Some people at this point would look to split the application out into multiple services or micro-services; however, that isn’t always very beneficial. Let’s take a walk down the path of Software Architecture and see what we can find.
Figure 1.
Many times, as people try and break free from the MVC
architecture, they go straight into something like Action
Domain Responder (ADR) or Domain Driven Design
(DDD)—but this isn’t the only approach you can take. Often,
borrowing from multiple different architectures can add a
real benefit to your application. For example, splitting your
application up into Domains and following an additional
architecture pattern to ensure that your code is easier to
read—and it is also maintainable and testable.
For the sake of anyone who may not know MVC yet, I will
do a quick tour of what it is and why it is. However, this article
is not designed to teach you what MVC actually is—please
look elsewhere for that! MVC is a fantastic pattern to use in
any application as you start building. It allows you to separate
your code into the behavioral logic and not mix the presentation into the business logic layer. In principle, this sounds
perfect, right? MVC is often a perfect fit for a simple website,
blog, or even e-commerce store! You lean on external services
for payment, shipping, and some of the more complicated
parts that require a dedicated service. Likely, this doesn’t
sound too strange for a lot of you reading this. However, as
your application grows and you start adding more and more
features or even integrating with more and more third-party
services, managing your application’s complexities can
suddenly become an uphill battle.
What MVC can give you is a solid foundation to build
upon. Some parts of your application may always stay within
this architectural approach. However, as your business logic
becomes increasingly important, and you aren’t just building
CRUD layers into your application—Software Architecture
quickly becomes your friend.
Let’s discuss ADR for a moment, as it is less spoken
about in the community. The Action Domain Responder
is actually quite a beautiful design pattern to lean on for a
growing application. The request comes into your application, which hits a controller. The controller will then call a
domain-specific action to perform some sort of logic. Once
the logic has been completed, the controller will then ask the
responder to respond to the request. This probably sounds
somewhat similar to the Action pattern that has been used
in the Laravel community for quite some time now. The key
difference here is that in ADR, the actions are typically split
into domain-specific areas, and a dedicated class is created to
handle responding. So think of it a little like how you might
use the Action pattern in Laravel but a little more in-depth
and advanced.
There are many benefits to this pattern; however, it has its
downsides, too. The biggest downside that I have experienced
is that code repetition can become a real problem—and it isn’t
really solving the main problem you fall into as your application scales. This is where I like to look beyond MVC and ADR
to see where I can take the application to really give it some
benefits. I have flirted with quite a few different approaches
in my time, and each approach is only as good as its implementation. However, I am quite a fan of the Vertical Slice
Architecture approach.
-----
Some of you, in fact, a lot of you, have likely heard of the
clean architecture approach. Well, the Vertical Slice Architecture (VSA) approach is almost the opposite of that. It is closer
to a modular design approach where each “slice” is a separation of specific functionality. However, just using this pattern
isn’t going to solve all of your problems. You really want to
pair this with another pattern so that you can really squeeze
the best out of your application and testing approach.
I like to pair something like VSA with DDD. What this
allows me to do is have features and domains separated nicely.
In fact, designing your domains around features or business
functions feels quite natural. If we were to take a standard
application and break it into Domain Driven Design for any
reason—we would likely split this by feature or business function. This is pretty much what you would do in Vertical Slice
Architecture, too. However, the key difference is that each
“Slice” in VSA doesn’t really talk to the other features. Each
feature is seen as independent, with no reliance or requirement on any other feature or slice.
Let’s take an example of a shipping application. We have
main business areas, such as Stock Management, Quality
Control, Logistics, Operations, and Finance, to name a few.
If we were to design a Shipping Management Application, we
would want to make sure that what we built replicated real life
as much as possible so that we were, in effect, mirroring the
physical world within the Digital world. Stock Management
would allow us to handle all of the inventory, re-ordering, and
suppliers. Quality Control is a part of the business that makes
sure that any inventory leaving the stock area on its way to the
logistics area is of the right quality so that it can protect the
brand and the buyer. Logistics, obviously, handles shipping
everything, as well as receiving any goods coming into the
business. Finance deals with payments, accepting payments
for the goods that are being sent out, as well as paying any
bills that need to go out. Finally, Operations is the part of the
business that ensures that the rest of the business is running
as it should be.
If we take the above idea and think of it in technical terms,
we can split the application into services, domains, and
features. We have the Stock management side, which is where
someone from the warehouse may look to see what is needed
for an order. There would be workflows built so that if stock of
a certain product got below a certain level, a new order could
be sent out through finance so that logistics could receive
the goods and put them back into the warehouse, ready to
be ordered by more customers. It is quite a beautiful process
when you look at it.
If we were building this in MVC, you can imagine how
messy that logic could get very quickly. However, if we split
this into slices using Vertical Slice Architecture, then we
would have different slices per business area. Then, within
each slice, we can split it further into domains or contexts.
Leveraging more than one approach can really help you to
fine-tune your application here.
There are always situations in software design that mean
you must make an exception. What do you do with code that
is intended to be shared? Or act as a pathway for one feature
to interact with another feature. All features should be able
to run independently of each other, but that isn’t how the
real world works. Why don’t we walk through a high-level
example of how the same code would be structured in each
approach, starting with MVC.
Let’s take the example of Logistics accepting a delivery,
which will start by accepting the delivery and then passing
this over to Stock Management. Stock Management will then
break the delivery down into two parts: items to store and
items on pre-order. Once items are stored, their stock amount
will increase. Any items that are on pre-order will be sent to
the Fulfilment team within the Stock Management department. If we approached this in an MVC pattern, it would be
quite large. There are a lot of different moving parts, and a
lot of this will be distributed across multiple classes. For this
example, I will show how it communicates to the external
class only. (See Listing 1)
Listing 1.
```
1. final class DeliveryAcceptedController {
2. public function __construct(
3. private readonly DeliveryService $deliveryService,
4. ) {}
5.
6. public function __invoke(
7. Request $request, string $delivery
8. ): JsonResponse {
9. // We start by validating our delivery
10. if (! $this->deliveryService->expecting($delivery)) {
11. throw new UnexpectedDeliveryException(
12. message: "Unexpected delivery found: {$delivery}",
13. );
14. }
15. // Next we want to ensure that the delivery
16. // has all the expected parts
17. if (! $this->deliveryService->validateContents(
18. $delivery, $request->get('items')
19. )) {
20. throw new DeliveryItemsMisalignmentException(
21. message: "Failed to validate contents of delivery,
22. manual check is required.",
23. );
24. }
25. // Next, send this over to the stock management team
26. dispatch(new DeliveryProcessed($delivery));
27. // Finally we want to ensure that we notify the
28. // Logistics team that this delivery has been processed.
29. return new JsonResponse(
30. data: [
31. 'message' => 'Delivery accepted.',
32. 'status' => DeliveryStatus::ACCEPTED,
33. ],
34. status: Status::ACCEPTED,
35. );
36. }
37. }
```
-----
As you can see, this is handled by an API, which is what I
would likely do in this scenario. We inject a Delivery Service
that contains the main business logic we may require. Then,
our Controller only really worries about ensuring that certain
preconditions are met before moving on. We want to make
sure that the delivery is an expected delivery as well as the
contents of the delivery are acceptable. Finally, we dispatch a
background job saying that the delivery has been processed,
which can emit any events as required and notify all relevant
people. Then, let the end user know that the delivery has been
accepted. Now, if we were to refactor this into ADR, then
we would do something relatively similar—but also ever so
slightly different. Let’s take a look at an example of this implementation. (See Listing 2)
Listing 2.
```
1. final class DeliveryAcceptedController
2. {
3. public function __construct(
4. private readonly DeliveryService $deliveryService,
5. private readonly DeliveryFailedResponder $failed,
6. private readonly DeliverySuccessfulResponder $responder,
7. ) {}
8.
9. public function __invoke(
10. Request $request,
11. string $delivery
12. ): JsonResponse {
13. // We want to validate the delivery is expected.
14. if (! $this->deliveryService->expecting($delivery)) {
15. return $this->failed->respondWithUnexpectedDelivery(
16. $request,
17. $delivery,
18. );
19. }
20.
21. //We still want to validate the contents of the delivery
22. if (! $this->deliveryService->validateContents(
23. $delivery, $request->get('items')
24. )) {
25. return $this->failed->respondWithInvalidContents(
26. $request,
27. $delivery,
28. );
29. }
30.
31. return $this->responder->deliverySuccessful($delivery);
32. }
33. }
```
the return type the same. The final step, when the successful
delivery is accepted—all logic to do with dispatching a job, etc.
(in other words, all behavioral logic) is then handled by the
responder. There are situations where, perhaps, a job is not
required to be dispatched that wasn’t thought of early on—but
the controller does not need to care about this. Its only job is
to accept the request, check for any early failure signs, and
return a response of some kind.
So, how would this be different if we were using the Vertical
Slice Architecture? Honestly, it wouldn’t be wildly different
in terms of the controller code—that would stay relatively
the same. However, the rest of the code being used would be
different, as would the structure and namespacing of the code
itself.
Vertical Slice Architecture wants you to split your application into as many independent features as possible, with each
feature having full control over the controllers, routing, and
anything else you might want to add to your application. Let’s
take a look at an example structure for this. (See Listing 3)
Listing 3.
```
1. └── app
2. ├── Deliveries
3. │ ├── Commands
4. │ ├── Controllers
5. │ │ └── AcceptNewDeliveryController.php
6. │ ├── DeliveryServiceProvider
7. │ ├── Events
8. │ │ ├── DeliveryAccepted.php
9. │ │ ├── DeliveryProcessed.php
10. │ │ └── DeliveryReceived.php
11. │ ├── Jobs
12. │ │ └── DeliveryProcessed.php
13. │ ├── Routes
14. │ │ ├── api.php
15. │ │ └── web.php
16. │ ├── Services
17. │ │ └── IncomingDeliveryService.php
18. │ └── Validators
19. │ └── NewDeliveryValidator.php
20. ├── Finance
21. ├── Logistics
22. └── StockManagement
```
The key difference you see here is that instead of throwing
an exception and letting this bubble up to our Exception
Handler, we are using a failure responder to handle how
we want to respond to a failure. It is not the concern of
the controller how the responder may respond other than
expecting it to return a JsonResponse that allows it to keep
Here, we start to register all of the possible classes that
focus on each “slice” or “domain”—however you may wish to
refer to it. This allows each slice to be able to register itself and
control how the application loads and sees its available classes
and resources. Doing it this way enables you to switch our
“slices” as required, as they should be independent of each
other—with little to no direct requirement on each other. This
approach is very testable and extremely resilient as it doesn’t
cross what we would call, in DDD terms, the “bounded-context”.
So, a question you may find yourself wondering is, how does
communication happen in the scenarios where Deliveries
-----
need to talk to Logistics, etc? This is where we look towards
async messaging, such as Apache Kafka or RabbitMQ, not
unlike the typical async Jobs we may use in any Laravel application. However, the key difference here is that the message is
dispatched to a consumer, who will instantly receive and start
handling the command itself. There is no adding to a queue;
it is a lot closer to how you might use events and listeners in
your Laravel application. Let’s have a look at an example of
the IncomingDeliveryService class. (See Listing 4)
What we do here is inject the distributed message client and
delivery repository into the constructor of this service, which
allows us to run specific database queries and send messages
to different areas of our application with little effort. Taking
this approach allows us to stay contained to our specific
“slice” or “domain” while leaning on the dependency injection
container to inject specific concrete instances of the dependencies we may have. This separates everything to a level that
makes our application easy to use, test, manage, and refactor.
From a testing perspective, we can test against specific functionality, outcomes, and side effects in each “slice” without it
being tied too closely to the implementation and structure we
are currently using.
The biggest downside to this approach is the manual setup,
as it makes most of the make generators in Laravel’s artisan
console redundant. You will get much better results using this
approach if you use a dedicated IDE that allows you to create
and refactor classes easily. While this may not be the typical
approach you see in Laravel development, it does lend itself
very well to a scaling application.
_Steve McDougall is a conference speaker,_
_technical writer, and YouTube livestreamer._
_During the day he works on building_
_API tools for Treblle, and in the evenings_
_spends most of his time writing content,_
_or contributing to the PHP open source_
_community. Whatever you do, don’t ask him_
_his opinion on twitter/X @JustSteveKing_
_[@JustSteveKing](https://twitter.com/JustSteveKing)_
Listing 4.
```
1. final class IncomindDeliveryService
2. implements CommunicationService
3. {
4. public function __construct(
5. private readonly MessagingClient $message,
6. private readonly DeliveryRepository $repository,
7. ) {}
8.
9. public function expected(string $delivery): bool
10. {
11. return $this->repository->expectingDeliveryId(
12. id: $delivery,
13. );
14. }
15.
16. public function validateContents(
17. array $items,
18. string $delivery
19. ): bool {
20. $valid = $this->repository->deliveryContainsItems(
21. items: $items,
22. id: $delivery,
23. );
24.
25. if (! $valid) {
26. $this->message->distribute(
27. new InvalidContents($items, $delivery)
28. );
29.
30. return false;
31. }
32.
33. return true;
34. }
35. }
```
**Harness the power of the Laravel**
**ecosystem to bring your idea to life.**
Written by Laravel and PHP professional
Michael Akopov, this book provides a concise
guide for taking your soft ware from an idea
[to a business. Focus on what really matters to](https://phpa.me/beyond-laravel)
make your idea stand out without wasting time
on already-solved problems.
###### Order Your Copy
**https://phpa.me/beyond-laravel**
-----
### Object Oriented Visibility
###### Chris Tankersly
Programming is weird. It is one of the newest disciplines that we have, but at the same time, it has been around since the 1950s. We seem to come up with concepts on an almost daily basis that, when dug into, we discovered years ago. It is almost like the jokes made about modern entertainment—all the good stories have been written, and all we can do is retell them. Sometimes, it feels like in the case of programming, all the good ideas have already been figured out; all we do is pretend we found them as brand new time and time again.
I have seen that, alongside this idea, the idea that the “old”
way of doing things gets thrown to the side because, well,
old languages did it, which means that today, in 2023, we no
longer need those paradigms. Not only do we spend our time
rediscovering things, we spend our time revolting against the
old ways.
While I am all for coming up with new and better ways of
programming, some things go together. In the case of PHP,
it is now an object-oriented language. The object model may
not be as robust as some other languages, but for the most
part, the language is treated as object-oriented and adheres
to most of those principles. One of those ideas is the visibility
of methods and properties in classes.
In the world of object-oriented programming, visibility,
often referred to as ‘access control’, plays a crucial role. It
governs the accessibility of class components. By adopting
visibility modifiers like `public,` `private, and` `protected,`
programmers can structure their code more deliberately and
establish boundaries, preventing any unintended or erroneous access.
###### Visibility Through the Ages
The foundational idea of visibility evolved alongside the
maturation of programming paradigms, especially object-oriented programming.
This starts with Simula, developed in the 1960s. Often
considered the first object-oriented language, Simula introduced the concepts of ‘classes’ and ‘objects’. While it set the
groundwork for encapsulation, Simula did not delve into
strict access controls or visibility as we understand it today.
It was important as it separated data and behavior into class
structures, setting the stage for more stringent access controls
in later languages. (See Listing 1)
Then came Smalltalk in the 1970s, developed at Xerox
PARC. This language took encapsulation even further. Smalltalk was among the first languages to stress the significance
of keeping an object’s data private, making it accessible only
through well-defined methods or ‘messages’. While lacking
the explicit ‘private’ or ‘public’ labels, Smalltalk’s architecture
made developers think about encapsulation and what the
objects exposed to the wider program. (See Listing 2)
We eventually got languages like C++ and Java, which
brought formal structures to visibility. C++ introduced
explicit keywords like public, private, and protected, granting
developers more control over class access. Java expanded on
Listing 1.
```
1. BEGIN
2. CLASS Person(name); NAME name;
3. BEGIN
4. OUTTEXT("Name of the person is: ");
5. OUTNAME(name);
6. OUTIMAGE;
7. END;
8.
9. Person Per1("John");
10. Person Per2("Alice");
11. END;
```
Listing 2.
```
1. Object subclass: Person [
2. Person class >> new: name [
3. | instance |
4. instance := self new.
5. instance initialize: name.
6. ^instance
7. ]
8.
9. | name |
10.
11. Person >> initialize: aName [
12. name := aName.
13. ]
14.
15. Person >> printName [
16. Transcript show: 'Name of the person is: ', name; cr.
17. ]
18. ]
19.
20. | per1 per2 |
21. per1 := Person new: 'John'.
22. per2 := Person new: 'Alice'.
23.
24. per1 printName.
25. per2 printName.
```
-----
###### j y
this, integrating its own set of visibility controls and introducing the ‘package-private’ visibility, the default visibility.
Such languages positioned visibility as a cornerstone of their
design philosophy, fostering safer and more maintainable
coding practices. (See Listing 3)
Listing 3.
```
1. #include
2. #include
3.
4. class Person {
5. private:
6. std::string name;
7. public:
8. // Constructor
9. Person(const std::string& aName) : name(aName) {}
10.
11. // Method to print the name
12. void printName() const {
13. std::cout << "Name is: " << name << std::endl;
14. }
15. };
16.
17. int main() {
18. Person per1("John");
19. Person per2("Alice");
20. per1.printName();
21. per2.printName();
22.
23. return 0;
24. }
```
As the software industry transitioned into the 21st century,
modern languages like Python, Ruby, and C# built upon the
principles set by their predecessors. For instance, Python’s
approach leaned towards convention over strict encapsulation, indicating ‘private’ attributes with a prefix underscore
(though it does have a concept it calls “name mangling” for
those times class-private items are needed).
It was not only about language syntax or conventions. The
idea of visibility became integral to design patterns and architecture. Even in contemporary paradigms like microservices,
service boundaries echo the principles of visibility but on a
grander architectural scale.
###### What is Visibility?
For the most part, modern object-oriented languages deal
with public, private, and protected visibility. Some languages
expand on this, like the remove ‘package-private’ idea in Java
or the name mangling in Python. Most languages that support
objects usually include these three main access controls.
These access controls generally work for both methods and
properties of an object. I will reference the combined idea of
methods and properties as just “members” of an object.
###### Public Visibility
When a member is declared as public, it means that it can
be accessed from any class or method, irrespective of whether
they are inside or outside its parent class. This level of accessibility embodies the idea of being “open to the world”. Such
openness has its merits. It is invaluable when creating components meant to be universally available, like API endpoints,
utility functions, or core functionalities of libraries and
frameworks. With this accessibility comes a responsibility. As
these members are exposed to the outside world, they become
critical touchpoints of interaction, and any change in their
behavior or structure could have far-reaching implications.
###### Private Visibility
At the other end of the spectrum lies the private modifier.
Members declared as private are, in essence, locked within
the confines of the class they belong to. No external class
can access them, making them the most restrictive in terms
of visibility. This kind of tight encapsulation is crucial for
maintaining the integrity of a class. By keeping certain mechanisms, helper methods, or specific data storages private,
developers ensure that external factors do not accidentally
introduce errors. It also gives developers the freedom to
make changes to these private members without the fear of
disrupting external systems that might rely on them. In many
ways, private visibility is about safeguarding and maintaining
a controlled environment for the class’s core functionalities.
###### Protected Visibility
Sitting between `public and` `private is the` `protected visi-`
bility. Members with this designation are accessible within
their parent class and also by any subclasses that inherit
from the parent. This particular visibility level shines when
building extensible systems. For instance, in designing a
base class that aims to be extended by various derived classes,
protected members serve as controlled touchpoints. They
offer a foundation that subclasses can build upon, adapt,
or override to suit their specific needs. I tend to default to
```
protected visibility in many of the libraries I build, as I expect
```
people to need to extend my base classes.
###### How This Works in Php
When you dive into PHP’s object-oriented nuances, it
becomes clear that its roots are not novel. PHP’s object model,
especially its implementation of visibility, is heavily influenced by Java. Many developers were already familiar with
the basic object syntax of languages like C++, and the additional robustness of Java’s system helped influence the early
PHP object model.
In PHP, like in Java, visibility is denoted using three primary
keywords: public, private, and protected.
- The public keyword, consistent with Java’s behavior, ensures that the member is universally accessible.
Such members are interfaces to the external world,
-----
facilitating interactions that are crucial for the class’s
purpose while shielding its inner mechanics.
- With private, PHP mirrors Java’s encapsulation
principle. Members are tightly bound within the class,
ensuring that their access and potential modifications
are controlled, thereby preserving the integrity of the
class’s behavior.
- The protected keyword, true to its Java counterpart,
offers a nuanced accessibility. These members are
available within the class and its subclasses, facilitating
inheritance by providing foundational elements that
derived classes can expand upon or change.
Drawing inspiration from our previous discussions, let’s
look at this with the `Person example, now represented for`
PHP: (See Listing 4)
Listing 4.
```
1. name . "\\n";
13. }
14.
15. // A protected method that can be used in child classes
16. protected function getFirstName() {
17. return explode(' ', $this->name);
18. }
19. }
20.
21. $per1 = new Person("John");
22. $per2 = new Person("Alice");
23.
24. $per1->printName();
25. $per2->printName();
```
In the above structure, PHP’s adaptation of Java’s visibility
principles is evident. By inheriting Java’s object model, PHP
offers web developers a coherent and potent paradigm. This
not only elevates the structural quality of web applications but
also ensures that developers transitioning from Java and other
object-oriented languages, or at least those familiar with its
principles, find PHP’s object-oriented programming features
intuitive and effective.
###### Why Visibility Matters
Encapsulation, one of object-oriented programming’s
cornerstones, thrives on visibility. By bundling data and
###### j y
methods within objects and setting appropriate visibility,
objects can manage their state, ensuring data integrity and
avoiding external disruptions.
Moreover, visibility plays a pivotal role in software maintenance and extensibility. By limiting access to class internals,
future modifications become safer and more straightforward.
Imagine the complications arising from a widely accessed
method undergoing change—without visibility restrictions,
this can break many dependent components.
Visibility also has a bearing on software security. Restricting
access to sensitive components, for instance, is fundamental
in applications handling sensitive data. A banking application
might want to keep account balance details under wraps, only
making deposit or withdrawal methods public.
After understanding the fundamental pillars of visibility
in object-oriented programming, a natural progression is
to delve into the intricacies of when and how to use these
visibility modifiers. Making the right choice can dictate
the success of a software project, its maintainability, and its
adaptability to changes.
At the heart of every decision about visibility lies a
programmer’s design philosophy. When determining the
visibility of a class member, it is important to think not only
about the immediate needs but also about the potential future
directions the software might take. For instance, exposing too
many components as `public might seem convenient in the`
short term, especially for easy access and testing. This can
lead to challenges in the future when these components need
modifications. External dependencies could break, leading to
a ripple effect of issues across the system.
On the other hand, being overly restrictive and keeping
too many components `private can hinder extensibility and`
potential integrations. The goal is to strike a balance, ensuring
that what needs to be kept internal remains so while allowing
flexibility where it is needed. Starting with a more restrictive
approach and then gradually opening up, based on tangible
requirements, often proves to be a prudent strategy.
Another critical consideration, especially when working in
collaborative environments, is the importance of clear documentation. A well-documented piece of code, complete with
rationale for visibility choices, can be invaluable for team
members, both present and future. For instance, when a
developer chooses protected visibility, indicating its intended
use and the reasons behind the decision can guide future
developers when they are extending the class or trying to
understand its architecture.
It is not just about explaining the choice of visibility but also
about conveying the broader context. If a method is made
```
public because It is expected to be a key integration point for
```
other systems, this needs to be documented. If a method is
```
private due to its handling of sensitive operations, flagging its
```
critical nature in documentation becomes essential.
While most of this discussion centers on object-oriented
programming, it is crucial to recognize that software development often involves blending different paradigms. A project
-----
###### j y
might integrate object-oriented programming with procedural or functional programming elements. In such scenarios,
understanding how visibility in object-oriented programming
interacts with similar concepts in other paradigms becomes
crucial.
In functional programming, while the exact terminologies might differ, there’s a concept of limiting the exposure
of certain functions or modules. When object-oriented
programming elements interact with functional components,
developers need to ensure that visibility choices in one paradigm do not inadvertently compromise the intentions of the
other.
In essence, the judicious use of visibility modifiers is not
only about understanding their definitions. Still, it is an art
that encompasses forward-thinking, clear communication,
and the ability to adapt to diverse programming landscapes.
Making informed decisions in this domain can set the stage
for software that’s robust, understandable, and ready for the
challenges of the future.
###### Sometimes Things Exist for a Reason
Emerging from historical roots with Simula’s rudimentary
encapsulation to the more defined paradigms of C++ and Java,
the concept of visibility has proven its worth. It is not only a
technical construct but a guiding philosophy that should be
thought about through every line of code, ensuring software’s
structural integrity, extensibility, and robustness.
A careful balance of `public,` `private, and` `protected`
modifiers, complemented by forward-thinking design and
comprehensive documentation, is instrumental in creating
software that stands the test of time. These decisions impact
not only the immediate functionality but also the adaptability
and resilience of systems in the face of evolving requirements.
In the vast and intricate landscape of programming, visibility
stands as an important paradigm that helps guide developers
toward crafting applications that are both powerful and reliable. As technology continues its relentless march forward,
the principles of visibility remain timeless, reminding us of
the delicate balance between accessibility and protection in
our digital creations.
_Chris Tankersley is a husband, father, author,_
_speaker, podcast host, and PHP developer._
_Chris has worked with many different frame-_
_works and languages throughout his twelve_
_years of programming but spends most of_
_his day working in PHP and Python. He_
_is the author of Docker for Developers and_
_works with companies and developers for_
_integrating containers into their workflows._
###### Docker For Developers is designed for developers looking at Docker as a replacement for development environments like virtualization, or devops people who want to see how to take an existing application and integrate Docker into their work- flow.
This revised and expanded edition includes:
• Creating custom images
• Working with Docker Compose and Docker Machine
• Managing logs
• 12-factor applications
Order Your Copy http://phpa.me/docker-devs
-----
-----
### PHP, Meet Passkeys
###### Eric Mann
Something you know, something you are, something you have. How does the new technology of passkeys fit into the proven authentication pyramid?
Typical authentication schemes are comprised of three
different ways to verify you are who you claim to be:
- **Something you are - this is typically your identifier.**
It could be a username, an email, or, in some cases, a
unique ID tied to you as an individual.
- **Something you know - this secret authentication**
factor helps prove you are who you say you are. It’s
typically a password or some other piece of secret information known only to you.
- **Something you have - a device tied to your account**
that acts as a redundant form of authentication proving
your intent to authenticate - even if someone managed
to guess your password, they will lack this second physical factor and be unable to authenticate.
Unfortunately, truly secure authentication is hard. Unique
identifiers are often public information. Users reuse passwords between various sites, enabling “credential stuffing”
attacks[1] that render even secure systems vulnerable to abuse.
Multifactor authentication (i.e., device-based second factors
or even SMS-based one-time tokens) isn’t universal and, even
when supported, is often misunderstood by end users.
In 2012, various organizations came together to found
the FIDO Alliance[2] to begin building an open specification
for password-free authentication. Today, this organization
counts major tech players like 1Password, Amazon, Apple,
Google, Microsoft, and Yubico as members. They’ve worked
together on various standards and protocols to make authentication easier for end users and more secure for developers.
One of their latest innovations is the passkey.
###### Introducing Passkeys
Thanks to standard FIDO protocols and support, passkeys[3] turn the devices you already know and trust into your
passwords. Rather than entering a new password, you merely
unlock your device using the same PIN or biometric control
you already use to unlock other applications.
It’s an incredibly smooth user experience and far superior
to asking someone to remember a long, complex password
for every site they use. Since you’re already likely unlocking
your phone and laptop with a PIN or biometric, this should
_1_ _[https://phpa.me/23andme-attack](https://phpa.me/23andme-attack)_
_2_ _[https://fidoalliance.org](https://fidoalliance.org)_
_3_ _[https://fidoalliance.org/passkeys/](https://fidoalliance.org/passkeys/)_
be a very familiar action that doesn’t interrupt your use of any
website or service.
Thanks to the underlying structure of the standards and
protocols involved, it’s also orders of magnitude more secure
than a typical password workflow, even one protected with
multifactor authentication!
###### How Passkeys Work
Longtime readers of Security Corner will be familiar with
my love for efficient, privacy-preserving authentication. In
2018, I covered an approach[4] to secure remote password
authentication leveraging Libsodium. In that scheme, your
password was converted to a public/private keypair that was
then used to sign a challenge issued by the server in order to
authenticate.
Passkeys work similarly, except without the use of a
user-defined password.
Instead, a public/private keypair is generated directly on
your device. The private key is stored in secure hardware,
while the public key is passed to the server during device
enrollment. Future authentication is performed by signing a
challenge from the server with your private key (again, within
secure hardware)—the signature is validated by the server
using your public key. (See Figure 1 on the next page)
Everything about these protocols is geared around privacy.
There’s no password to exchange, just a public key and a signature, both tied to some random key created on the device.
There’s also no possibility of `leaking the private key since`
it lives within the secure hardware embedded in the device.
The FIDO protocol also supports a different set of keys per
site. Nothing is reused. Nothing can be phished. Nothing
can fall victim to credential stuffing. You’re relying on secure
hardware and strong cryptographic protocols every step of
the way!
_4_ _[https://phpa.me/security-july-2018](https://phpa.me/security-july-2018)_
-----
###### PHP, Meet Passkeys
Where to Use Them
Websites and applications are opening the door to passkeys
daily. There’s a high likelihood that your existing devices are
compatible and you’re ready to get started.
If you use an iOS device, an Android device, or a modern
Mac or Windows computer, you likely have the requisite
hardware and software already. ChromeOS supports passkeys
in some situations, and even Ubuntu supports them when
using Chrome or Edge browsers.
Google, PayPal, Instacard, Tailscale—the list of sites and
services that support passkeys[5] is frequently growing. There
are also plenty of FIDO-compliant and webauthN[6] libraries
available that empower you to support passkeys in your own
PHP application.
User security is hard. Passwords are hard. Multifactor
authentication is hard. None of these statements need to
remain true—we have newer, better alternatives that we can
and should leverage to make the next generation of PHP
applications easier and more efficient for our customers.
_Eric is a seasoned web developer experi-_
_enced with multiple languages and platforms._
_He’s been working with PHP for more than_
_a decade and focuses his time on helping_
_developers get started and learn new skills_
_with their tech of choice. You can reach out_
_[to him directly via Twitter: @EricMann](https://twitter.com/EricMann)_
_5_ _[https://www.passkeys.io/who-supports-passkeys](https://www.passkeys.io/who-supports-passkeys)_
_6_ _[https://phpa.me/webauthn-framework](https://phpa.me/webauthn-framework)_
###### 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 fi rst principles.
Why wait until your site is attacked or your data is
[breached? Prevent your exposure by being aware of the](https://phpa.me/security-principles)
ways a malicious user might hijack your web site or API.
###### Order Your Copy https://phpa.me/security-principles
Figure 1.
-----
-----
### WebAuthn: The Future to Securing Applications
###### Matt Lantz
Online security is a predominant concern among most companies and developers. Traditional authentication methods, such as passwords, have proven to be increasingly vulnerable to hacking and phishing attacks.
Corporations’ yearly five-minute
videos that they force employees to
watch do not address the root problem.
To address this growing concern, a
new standard called WebAuthn has
emerged as a groundbreaking solution
that promises to revolutionize online
authentication. Furthermore, the
WebAuthn Framework offers an elegant
means of using this new technology
with your standard PHP applications.
First, what is WebAuthn?
WebAuthn, short for Web Authentication, is a web standard developed by the
World Wide Web Consortium (W3C)
and the FIDO Alliance. Its primary goal
is to provide a secure and easy-to-use
framework for authenticating users on
the web using public-key cryptography.
This standard enables web applications
to utilize hardware-based authenticators, such as biometric sensors, USB
tokens, or mobile devices, to verify a
user’s identity without requiring passwords, fundamentally password-less
authentication.
It works by employing a two-factor
authentication approach that combines
“something you have” (the authenticator) and “something you know” (a
user gesture like a PIN or fingerprint)
to establish a user’s identity. The process
involves the following steps:
Registration: When users attempt to
authenticate on a website supporting
WebAuthn, they are prompted to
register their authenticator device by
binding it to their account. Authentication: During subsequent logins, the
website challenges the user’s authenticator to prove their identity.
It sounds pretty simple upfront, but
there are many layers beneath this
where the framework’s value comes in.
Nearly all browsers support WebAuthn,
and adaptor packages for Symfony and
Laravel are available. One important
thing to remember before you get too
excited about this authentication system
is that it is JavaScript API-driven, so
beyond what packages cover, you may
need to custom-write some JavaScript
to fill in gaps and add details.
So, what are the overall benefits of
implementing WebAuthn?
We have to look at WebAuthn as a
way of making your application passwordless, meaning you would no longer
be susceptible to a wide range of attacks
|Relying Party Server 6|Col2|Col3|
|---|---|---|
|llenge, r info, 1 party info||0|
|RP JavaScript Application|||
|Browser|||
|g party id , er info, 2 party info, DataHash|||
|Authenticator|||
This standard enables web applications be susceptible to a wide range of attacks
**Relying Party Server** **6** server validation
challenge,
clientDataJSON,
**PublicKeyCredentialCreationOptions** user info, **1** **0** **5** **AuthenticatorAttestationResponse**
attestationObject
relying party info
**RP JavaScript Application**
**Browser**
relying party id ,
new public key,
user info,
**2** **4** credential id, **attestationObject**
relying party info,
attestation
clientDataHash
**Authenticator**
**3**
user verification,
new keypair,
attestation
-----
###### WebAuthn: The Future to Securing Applications
that OWASP outlines. It removes the threat of users using old,
vulnerable passwords or requiring users to change their passwords consistently.
It enhances user experience as your application can utilize
biometrics such as a finger tap, a photo, or a phone notification. There is no need to remember a password; furthermore,
you don’t even need a password manager tool to handle
“auto-generating” a complex password. There is also a focus on
privacy within WebAuthn since biometric data is processed
locally and never leaves the user’s device.
Phishing attacks also become stale since WebAuthn
requires the device to be in the hands of the user at the time
of its processing. Attempts at using fake websites to steal user
credentials become useless.
As I’ve mentioned, WebAuthn is supported by all major
browsers and has been adopted by the technology giants
Apple, Google, and Microsoft. This leaves us with the question many of you started reading this article for:
How do I implement it?
If you’re on a larger team and really need to build your
integration from scratch, go for it. Most auth systems are best
when the open-source community can help ensure they are
stable and up to date with the best security practices and big
fixes. Implementing the WebAuthn framework from scratch
requires the following components in your PHP codebase.
- An Attestation Statement Support Manager
- At least one Attestation Statement Support object
- An Attestation Object Loader
- A Public Key Credential Loader
- An Extension Output Checker Handler
- An Algorithm Manager
- An Authenticator Attestation Response Validator
- An Authenticator Assertion Response Validator
Many of these components are between 5 to 10 lines
of code - thanks to the WebAuthn framework - but each
comes with various details that are impacted by whichever
PHP framework you use. Beyond the above, you still need
to implement the registration of users, storage of public key
information, and, lastly, configure the actual authentication measures within your application, which may include
writing middleware or firewalls. Furthermore, registration
and subsequent login pages must contain some HTML and
JavaScript to collect the appropriate WebAuthn credentials.
The WebAuthn-framework recommends using this package
to handle the JavaScript side of events:
https://phpa.me/webauthn-browser[1].
This JavaScript package predominantly handles browser
permissions and integrates the various AJAX requests that
perform the above validations. Overall, I do not recommend building an implementation of this from scratch.
When reading through the documentation surrounding the
framework, I found numerous “gotchas” outlined in their
_1_ _[https://phpa.me/webauthn-browser](https://phpa.me/webauthn-browser)_
documentation. I recommend using open-source packages
for whichever PHP framework you are currently using. Below,
I will outline how to implement a Laravel-based package I
encountered in my research. I chose this one because it’s on
version 4.x and has over 400,000 downloads.
The webauthn-framework comes with a Symfony bundle
baked in, but for Laravel developers, I currently recommend
using `asbiin/laravel-webauthn, which handles integrating`
the webauthn core elements into your application, including
the bulk of the JavaScript coding required.
Installation as simple as running: composer require asbiin/
```
laravel-webauthn guzzlehttp/psr7
```
Then you need to ensure that you publish the configuration
as well by running `php artisan vendor:publish --provid-`
```
er="LaravelWebauthn\WebauthnServiceProvider"
```
Lastly, as usual, run a migration with php artisan migrate.
Assuming you’re working with a standard Laravel application, add the following to the $routeMiddleware array of your
```
app/Http/Kernel.php file.
use LaravelWebauthn\Http\Middleware\WebauthnMiddleware;
...
'webauthn' => WebauthnMiddleware::class,
```
Within your routes, you can then add it as a middleware:
```
Route::middleware(['auth', 'webauthn'])
->group(function () {
...
});
```
Lastly, to enable the passwordless authentication, you need
to set your driver in your config/auth.php
```
'providers' => [
'users' => [
'driver' => 'webauthn',
'model' => App\Models\User::class,
],
],
```
This package is intended to be used with standard responses
and page loads, so if you’re using an SPA approach, you need
to disable the views, which you can do in the package configuration:
```
'views' => false,
```
If you’re currently using Inertia, you can see an example
application using this package, which the maintainer Alexis
also made available here:
https://phpa.me/laravel-webauthn-example[2]
You can easily customize the bulk of the view files in this
package to suit your application requirements. Further, there
are customizable options similar to Laravel Fortify, which
can help developers customize the Authentication Pipeline
process.
_2_ _[https://phpa.me/laravel-webauthn-example](https://phpa.me/laravel-webauthn-example)_
-----
To enable further layers, you can set the method `Webau-`
```
thn::authenticateThrough in your boot method in the
FortifyServiceProvider class as shown in Listing 1.
```
Listing 1.
```
1. Webauthn::authenticateThrough(
2. function (Request $request) {
3. return array_filter([
4. config('webauthn.limiters.login')!== null ?
5. null :
6. EnsureLoginIsNotThrottled::class,
7. AttemptToAuthenticate::class,
8. PrepareAuthenticatedSession::class,
9. ]);
10. }
11. );
```
There are other options you can configure, as well as the
rate-limiting, which uses email and IP by default. There is also
a set of events you can tie into based on your application’s
needs.
```
On login with Webauthn check.
\LaravelWebauthn\Events\WebauthnLogin
On preparing authentication data challenge.
\LaravelWebauthn\Events\WebauthnLoginData
On a failed login check.
\Illuminate\Auth\Events\Failed
On registering a new key.
\LaravelWebauthn\Events\WebauthnRegister
On preparing register data challenge.
\LaravelWebauthn\Events\WebauthnRegisterData
On failing registering a new key.
\LaravelWebauthn\Events\WebauthnRegisterFailed
```
If you wish to add further details or customize the payload
in the various responses from the WebAuthn sequences, you
can bind it using the register method of your AppServicePro```
vider.
use LaravelWebauthn\Services\Webauthn;
class AppServiceProvider extends ServiceProvider
{
public function register()
{
Webauthn::loginViewResponseUsing(
LoginViewResponse::class
);
}
}
```
Then your LoginViewResponse can look something like the
following:
###### WebAuthn: The Future to Securing Applications
```
class LoginViewResponse extends LoginViewResponseBase
{
public function toResponse($request)
{
$publicKey = $this->publicKeyRequest($request);
return Inertia::render('Webauthn/WebauthnLogin', [
'publicKey' => $publicKey
]);
}
}
```
Overall, the following responses can be overridden so long
as they align with the corresponding contract:
**Response** **Contract**
loginViewResponseUsing LoginViewResponseContract
loginSuccessResponseUsing LoginSuccessResponseContract
registerViewResponseUsing RegisterViewResponseContract
registerSuccessResponseUsing RegisterSuccessResponseContract
destroyViewResponseUsing DestroyResponseContract
updateViewResponseUsing UpdateResponseContract
Lastly, to ensure that you do all this work successfully, you
must have a secure domain set up locally.
- proper domain (localhost and 127.0.0.1 will be rejected
by webauthn.js)
- SSL/TLS certificate trusted by your browser
- HTTPS port 443 (ports other than 443 will be rejected)
WebAuthn is a game-changer in online authentication,
offering a more secure, user-friendly, and interoperable
approach to identity verification. WebAuthn has the power
to mitigate the risks of credential theft and phishing attacks,
which means it will only become more popular. As the world
becomes increasingly dependent on digital interactions,
adopting WebAuthn will be crucial to ensuring trust and
confidence in online platforms.
_Matt has been developing software for over_
_13 years. He started his career as a PHP_
_developer for a small marketing firm but_
_has since worked with a few Fortune 500_
_companies, led a couple teams of developers,_
_and is currently a Cloud Architect for a_
_significant Travel technology company. He’s_
_contributed to the open-source community_
_on projects such as Cordova and Laravel._
_He also made numerous packages and has_
_helped maintain a few. He spends time with_
_his wife and kids when he’s not tinkering_
_with code or learning new technologies._
_[@Mattylantz](https://twitter.com/Mattylantz)_
|Response|Contract|
|---|---|
|loginViewResponseUsing|LoginViewResponseContract|
|loginSuccessResponseUsing|LoginSuccessResponseContract|
|registerViewResponseUsing|RegisterViewResponseContract|
|registerSuccessResponseUsing|RegisterSuccessResponseContract|
|destroyViewResponseUsing|DestroyResponseContract|
|updateViewResponseUsing|UpdateResponseContract|
-----
### Done For You
###### Maxwell Ivey
This time I want to talk about an approach to improving accessibility that would probably be controversial to long-time advocates in the world of accessibility and inclusion. It is the idea that ‘done-for-you’ has a real place in accessibility.
To me, the goal should be to build a
website or app in such a way that I can
easily accomplish the goals the site was
developed for in the first place. I want to
be able to find my way around and take
positive actions with as little thought
and as much joy as possible.
Given the limitations of the available adaptive technology, the variety
of disabilities, and the varying levels
of proficiency with technology among
users, sometimes ‘done-for-you’ could
be the easiest way to make my life better.
This is especially true in the process
of updating a system to include accessibility.
In fact, several of the companies
and organizations I have worked with
over the years required us to take this
approach.
They had limited time and other
resources.
With Nepris now called Pathful, an
online education platform, I wouldn’t
have even been able to create my profile
when I first discovered it in 2018. In
the beginning, they created my profile,
set up subject matter notifications,
accepted available presenter slots, and
even helped me get logged into their
proprietary meeting platform. Without
their help, I would have missed the
opportunity to share my knowledge
and experience with thousands of
young people across the country.
But even now, they still do a lot for
me. Not because I need them to, but
because it makes my life easier to allow
them to.
Their site is now highly accessible.
A member of their team regularly
monitors new classroom requests and
emails me when they find one they
think I would be a good fit for. That
means I don’t have to sift through
the new listings on their site or work
through lots of email notifications.
And because we built a relationship,
my contacts there have become friends
who support me in my activities.
With Pod Match, a site that matches
up podcast hosts and interested guests, I
would have missed many opportunities
to find perfect guests for my podcast or
to be interviewed by awesome hosts.
Those interviews allowed me to
share my message, spotlight incredibly
inspiring people, and build lasting relationships with fellow podcasters.
They had to create my original profile.
They had to help me figure out how
to navigate to their advanced search
engine.
Recently, one of the businesses I am
working with decided to take their
team communications to Coda. When
I visited their intake form for the group,
I was only able to navigate half of the
fields. Instead of getting upset, I reached
out to them.
When they told me they would work
on the accessibility issues, I approached
the issue with my usual problem-solving
mindset. I asked them if they would be
willing to fill out my form for me if I
provided the answers to the questions.
I could read the field labels with my
screen reader, but I couldn’t enter the
required information in all of them.
They wrote back saying that they were
willing. It turned out that I forgot a few
of the fields, and their customer rep
prompted me to provide the answers I
had left out.
As a result, I now have a team profile
there, and we are having conversations
about accessibility.
There are a couple of examples
where businesses use the ‘done-for-you’
approach on a larger formal scale.
For example, there is the accessibility-focused customer support team
at Apple. They provide a different
contact number where you reach a
team of people who have been specifically trained to answer questions about
Apple technology being used with adaptive technology options like their screen
reader voice-over.
Another one is my hosting provider,
HostGator. Their site is mostly accessible, but there are things that it is just
easier to let a trained, experienced
support person do for you.
I recently filed for a new domain
name for my new podcast, The Accessibility Advantage, and as the home of my
accessibility consulting website.
I called them up and asked them to
help me activate the site, redirect the
DNS name servers, turn on the security
setting that makes a site HTTPS, and
install WordPress for me. They have
a dedicated 800 number for people
with disabilities to call with problems.
Their site is highly accessible, but it’s a
huge site. And some of the functions
you want to perform have dangerous
possible effects if you make a mistake.
So, I am very happy to let them do it
for me.
This whole attitude goes back to my
early childhood education about my
expected vision loss from RP, retinitis
pimgentosa. My dad told me repeatedly
never to be afraid to ask for help. As
a result, I am not as fiercely independent as the instructors of people with
disabilities encourage us to be. I am
also more willing to work with people
and accept their help than many of my
disabled friends.
I believe in interdependence alongside or over independence. I didn’t even
know the word inter-dependence, but I
-----
knew that difficult, challenging things are not meant to be
done alone.
Now, how do you take advantage of this?
First, by evaluating what the most important things are
about someone using your website or app. For example, the
most important part of an airline booking site is being able to
choose your ticket and purchase it. Then, based on the reason
behind the content in the first place, try to see whether you
can design a navigation option that would allow a disabled
person to operate your website or app successfully.
Then, consider whether or not it might make your life
and the lives of your disabled users easier by offering a
‘done-for-you’ option. This could be for highly complicated
operations or for functions that involve manipulating images
or videos.
Once you identify these functions where ‘done-for-you’
is the best option for everyone, then create a notification to
let the disabled person know how to proceed. This is usually
done by having a text message, a link, or both saying if you
have a disability, then please contact us here.
Naturally, you have to create the system to support that
option. You have to make sure those contacts go to an individual or a team capable of handling the requests and prepared
to receive them. And you need a way to track those interactions to make sure the proper department is handling them
promptly, efficiently, and personably. It would help if you
realized that not everyone is going to accept ‘done-for-you’
as an accessibility answer. Many are too caught up in doing
things by the book, such as the WCAG or the ADA, and they
can’t accept answers based on a pragmatic problem-solving
approach.
_Maxwell Ivey is known around the world as_
_The Blind Blogger. He has gone from failed_
_carnival owner to respected amusement_
_equipment broker to award-winning author,_
_motivational speaker, online media publicist,_
_and host of the What’s Your Excuse podcast._
_[@maxwellivey](https://twitter.com/maxwellivey)_
###### php[architect] Get
**customized**
**solutions for**
###### [consulting] your business
**needs**
**Leverage the**
**expertise of**
**experienced**
**Create a**
**PHP**
**[dedicated team](mailto:consulting%40phparch.com?subject=Consulting%20Service%20Request%20-%202311)**
**developers**
**or augment**
**your existing**
**team**
**Building**
**cutting-edge**
**solutions using**
**Improve the** **today's**
**performance** **development**
**and scalability** **patterns**
**of your web** **and best**
**applications** **practices**
**consulting@phparch.com**
-----
# Real SolutionS Real netwoRkS
# Real netwoRkS
###### ADMIN is your source for technical solutions to real-world problems.
Improve your admin skills with practical articles on:
• Security
• Cloud computing
• DevOps
• HPC
• Storage and more!
Get it FASt with a digital subscription!
@adminmagazine @adminmag
6 issues per year!
Order NOW
-----
### Shellsort
###### Oscar Merida
Much like Comb Sort improved on the Bubble Sort, Shellsort also improves the execution of the Insertion Sort. Again, the improvements come from comparing items that are far apart in the array in early iterations until we are comparing adjacent items again.
###### Recap
_For next month, write an implementation of the Shellsort,_
_a variant of the insertion sort. Generate an array of N_
_random numbers. Pick as large a range as you want to_
_work with, but don’t make the range too small, and use_
_your comb sort function to order it._
_Again, generate many such arrays, and collect and present_
_data on how long it takes to sort. Show the shortest,_
_longest, and mean times._
###### Algorithm
Let’s recall that the insertion sort works. On any pass, k, we
look at the items from the beginning of the array through the
```
kth element. For the element at spot k, we find where in that
```
part of the array it belongs and insert it into the right spot.
Thus, after a pass, the elements in spots 0 through k are properly sorted. Each subsequent pass is trying to figure out where
the next element, k+1, belongs in that “trailing” sorted array.
This inspection requires many comparisons between adjacent elements. Like the bubble sort, it means moving items
that should be at the very front or very end of the array to their
correct spot requires multiple calls to our `swap_elements()`
function. How can we get items near their final spot quicker?
The comb sort improved performance by comparing
more distant elements in our arrays. Shellsort takes a similar
approach by comparing sets of elements that are some “gap”
distance apart.
Again, turn to the Wikipedia article for shellsort[1] for an
explanation of the pseudocode to implement it. If, like me,
you’re a visual learner, the animations on Wikipedia are
invaluable for grokking these sorts. (See Listing 1)
We’ll come back to what the sequence of gaps might be. The
original algorithm proposed by Shell used a gap that decayed
exponentially. The first time, the gap was n/2, where n is the
number of array elements. Each subsequent gap is one-half of
the previous one until the gap is 1. At that gap, the shell sort is
identical to the insertion sort.
_1_ _[https://en.wikipedia.org/wiki/Shellsort](https://en.wikipedia.org/wiki/Shellsort)_
Listing 1.
```
1. # Start with the largest gap and work down to a gap of
2. # 1 similar to insertion sort but instead of 1, gap is
3. # being used in each step
4. foreach (gap in gaps)
5. {
6. # Do a gapped insertion sort for every elements in
7. # gaps. Each loop leaves a[0..gap-1] in gapped order
8. for (i = gap; i < n; i += 1)
9. {
10. # save a[i] in temp and make a hole at position i
11. temp = a[i]
12. # shift earlier gap-sorted elements up until the
13. # correct location for a[i] is found
14. for (j=i; (j>=gap) && (a[j - gap]>temp); j -= gap)
15. {
16. a[j] = a[j - gap]
17. }
18. # put temp (original a[i]) in its correct location
19. a[j] = temp
20. }
21. }
```
###### Solution
Listing 2 (on the next page) shows an implementation of
the Shellsort using that gap sequence.
To understand what’s happening, let’s look at the first pass.
We have the following array with eight elements. Our first gap
size is 4.
```
9 15 24 19 2 1 3 21
```
That means we create set of all elements 4 spaces apart:
```
9 15 24 19 2 1 3 21
9 -------- 2
15 ------- 1
24 ------ 3
19 ----- 21
```
For each pair, in this case, we use an insertion sort to order
them. In this case, we’re swapping only two elements. We
make the following swaps. After that pass, our array is sorted
as shown at the end. Our smaller numbers are much closer to
the start of the array, and the larger numbers are near the end.
-----
###### S e so t
```
2 ------ 9
1 ------ 15
3 ------- 24
19 ------ 21
2 1 3 19 9 15 24 21
```
On the next pass, our gap is 2 – one-half of 4, and the sets
we sort are:
```
2 1 3 19 9 15 24 21
2 - 3 -- 9 -- 24
1 - 19 - 15 -- 21
```
Each set gets sorted and combined:
```
2 - 3 -- 9 -- 24
1 - 15 - 19 -- 21
2 1 3 15 9 19 24 21
```
Finally, the next gap is 1 and our final gap. This sorts the
array using a regular insertion sort but with more elements
close to where they should be.
```
2 1 3 15 9 19 24 21 Start
1 2 3 15 9 19 23 21 Sort 0...1 reqires 1 insertion
1 2 3 15 9 19 23 21 Sort 0...2, already sorted
... Sort 0...3, 0...4 already sorted too
1 2 3 9 15 19 23 21 Sort 0...5, requires 1 insertion
Sort 0...6, 0..7 already sorted
1 2 3 9 15 19 21 23 Sort 0...8 requires 1 insertion
###### Benchmarks for Exponential Gaps
```
###### Ciura’s Gap Sequence
The gap sequence we chose to use can greatly affect the
performance of the Shellsort. Too many gaps produce overhead, while two few gaps make subset insertion sort slower.
The Wikipedia article lists a baker’s dozen of proposed gap
sequences and their theoretical performance. Most, like
the exponential sequence, are based on the size of the array.
Cirua’s sequence is experimentally derived and minimizes the
average number of comparisons. The sequence is surprisingly
short, though the Wikipedia article gives a formula for calculating larger sequences.
```
1, 4, 10, 23, 57, 132, 301, 701
```
Listing 3 (on the next page) shows a Shellsort that uses
Ciura’s sequence for gaps.
###### Cirua Benchmarks
**Shellsort C** **1000** **5000** **10000**
Fastest 0.01789 0.1333 0.3559
Slowest 0.03301 0.2012 0.5008
Mean 0.02097 0.1594 0.4106
###### Comparison
The table below shows how the sorting algorithms we’ve
looked at compare. Curia’s sequence of gaps is faster than the
exponential gaps from our first implementation. Its mean
performance does not suffer as the array size gets larger
compared to the first. Surprisingly, the Comb sort is still king
of the hill. Figure 1 on the next page shows that the Shellsort
performance is in the middle of the pack.
|Shellsort C|1000|5000|10000|
|---|---|---|---|
|Fastest|0.01789|0.1333|0.3559|
|Slowest|0.03301|0.2012|0.5008|
|Mean|0.02097|0.1594|0.4106|
|Shellsort 1|1000|5000|10000|
|---|---|---|---|
|Fastest|0.02035|0.1704|0.3968|
|Slowest|0.03222|0.2394|0.9712|
|Mean|0.02462|0.2003|0.6082|
Listing 2.
```
1. function shell_sort_ciura(array &$list): void
2. {
3. $data = [1, 4, 10, 23, 57, 132, 301, 701];
4. $gaps = array_reverse($data);
5.
6. foreach ($gaps as $gap) {
7. if ($gap > count($list)) continue;
8.
9. $i = $gap;
10. while ($i < count($list)) {
11. for ($j = $i;
12. ($j >= $gap && $list[$j-$gap] > $list[$j]);
13. $j -= $gap
14. ) {
15. swap_elements($list, $j, $j - $gap);
16. }
17. ++$i;
18. }
19. }
20. }
```
|Sort|1000|5000|10000|
|---|---|---|---|
|Bubble|0.0321|0.8414|3.3420|
|Insertion|0.0249|0.6525|2.7604|
|ShellSort 1|0.0246|0.2003|0.6082|
|ShellSort C|0.0210|0.1594|0.4106|
|Comb|0.00873|0.06275|0.1286|
-----
###### Shellsort
```
array &$list, int $i, int $j): void
($i, $list) &&
($j, $list)
[$i];
= $list[$j];
= $tmp;
InvalidArgumentException(
"Invalid index specified"
* We're assuming sequential integer keys
, scalar> $list
array &$list): void
count($list) / 2);
0) {
< count($list)) {
$i;
>=$gap && $list[$j - $gap]>$list[$j]);
-= $gap
$list, $j, $j - $gap);
($gap / 2);
```
_Oscar Merida has been working with_
_PHP since version 4 was released and is_
_constantly learning new things about it and_
_still remembers installing an early version_
_on Apache. When not coding or writing, he_
_[enjoys RPGs, soccer, and drawing. @omerida](https://twitter.com/omerida)_
Figure 1.
```
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
```
_2_ _[https://psysh.org](https://psysh.org)_
-----
### Is Your Code Abstracted Enough To Minimize Load?
###### Christopher Miller
Last month, we looked at the handling of plans—well, building from that, we’re going to start delving into abstraction and minimizing load. What do I mean, though, by abstraction?
Abstraction is a fundamental concept in programming that
helps us manage complexity. At its core, abstraction means
hiding the unnecessary details and showing only the essential
parts of something. Let’s break it down:
Imagine you’re driving a car. When you press the accelerator, the car moves forward, right? You don’t need to know
how the engine, transmission, or fuel injection system work
under the hood to make the car go. That’s abstraction in
action. You interact with a simple interface (the pedal), and
the complex mechanisms are hidden from you.
In code, abstraction means creating a simplified, high-level
representation of a more complex system. You expose only
the necessary functionalities while hiding the implementation details. For example, in object-oriented programming
(OOP), you use classes to define objects. The class defines the
structure and behavior of an object, and you interact with it
through methods and properties.
Let’s say you’re building a banking application. You
create a BankAccount class. Users of your code don’t need
to know how transactions are stored in the database or how
interest calculations are done. They interact with the `Bank-`
```
Account object through methods like deposit, withdraw, and
getBalance. The underlying complexities are abstracted away,
```
making it easier for both developers using your code and
users of the banking application.
Abstraction not only simplifies the interaction but also
allows for flexibility and easier maintenance. If you ever need
to change how interest is calculated, you can do it inside the
```
BankAccount class without affecting the rest of your application.
```
This is a key principle of object-oriented programming and a
great way to manage complexity in software engineering.
###### What Load are We Minimizing?
Here, we are specifically talking about the cognitive load of
reading and understanding the code.
In the context of a developer reading code, cognitive load
refers to the mental effort and capacity required to understand, analyze, and work with a piece of code. It’s a critical
aspect of software development, and reducing cognitive load
is essential for writing clean, maintainable code. Let’s break
down cognitive load in this context:
- **Code Complexity: The complexity of the code, such as**
nested loops, conditional statements, and deep function
calls, can increase cognitive load. Simple, well-structured code is easier to understand.
- **Inconsistent Style: If code lacks a consistent style and**
follows no established conventions, it can be mentally
taxing to decipher. Consistent naming, formatting, and
organization help reduce cognitive load.
- **Comments and Documentation: Well-documented**
code reduces cognitive load significantly. Comments
explain the purpose of functions, classes, or sections of
code, making it easier for a developer to understand the
intent.
- **Modularity: Breaking code into smaller, reusable**
modules and adhering to the principles of OOP can
make it more understandable. A well-designed class or
function should have a single responsibility, reducing
cognitive load.
- **Variable and Function Names: Descriptive and mean-**
ingful variable and function names can lower cognitive
load. Developers should be able to infer the purpose of
a variable or function just by looking at its name.
- **Avoiding Magic Numbers and Strings: Using**
constants or enums instead of magic numbers or strings
enhances code clarity. It reduces the need to remember
what specific values mean.
- **Code Optimization: Highly optimized code can**
be harder to understand due to the use of intricate
algorithms or techniques. Balancing optimization with
readability is crucial.
- **Version Control: An organized version control system**
like Git can help reduce cognitive load by allowing
developers to trace the history of code changes and
understand the context behind them.
- **Testing and Debugging: Proper unit tests and**
debugging tools can lower cognitive load by helping
developers quickly identify and fix issues.
- **Dependency Management: Clear management of**
dependencies and libraries can simplify code comprehension. Overly complex dependency structures can
increase cognitive load.
-----
###### Is Your Code Abstracted Enough To Minimize Load?
- **Domain Knowledge: Understanding the problem**
domain is vital. When developers have a deep understanding of the problem they are solving, it reduces
cognitive load as they can relate the code to the realworld context.
###### An Example of Badly Abstracted Code
So, now we have an idea of what Abstraction and Cognitive Load are, let’s look at a badly abstracted code example.
We’re going to use a simplified Shopping Cart System, Listing
1 gives us our starting point.
Let’s break down why the provided code is badly abstracted
and explore the problems in detail.
- **Lack of Separation of Concerns:- The Shopping-**
Cart class handles both storing products and calculating the
total cost. This is a violation of the Single Responsibility Principle (SRP) in object-oriented programming.
- Ideally, there should be a separation between the
data storage (shopping cart) and the logic to calculate the
total cost. This lack of separation makes the code less modular
and harder to maintain.
- **Conditional Logic for Tax Calculation:- The**
```
calculateTotal method contains conditional statements to
```
determine the tax rate based on the product’s category.
- This conditional logic for tax calculation makes the
code more complex and harder to read. If new categories or
tax rules are introduced, this code would need modification,
leading to potential errors.
- **Limited Reusability:- The tax calculation logic is**
tightly coupled with the ShoppingCart class. This means you
can’t easily reuse this logic in other parts of your application.
- Abstraction would allow you to encapsulate tax
calculation in a separate, reusable component, making your
code more modular and efficient.
- **Poor Scalability:- As the codebase grows, manag-**
ing product categories and tax rates within the calculateTotal
method becomes increasingly challenging.
- This lack of abstraction and organization can lead to
spaghetti code, making maintenance and scaling a nightmare.
So, as you can see, this becomes really complicated to
understand very quickly. Imagine using this on a store the
size of Amazon. It would be impossible to manage. Let’s take
the steps in turn and improve our code to be clearer.
###### Separation of Concerns
Separation of Concerns (SoC) is a fundamental software design principle in object-oriented programming,
emphasizing the division of a software system into distinct,
self-contained modules or classes, each responsible for
a specific aspect of the application. This principle helps
in managing complexity, improving maintainability, and
promoting code reusability. Let’s explore how SoC applies
to the original code and then how we can restructure it to
adhere to this principle.
**Original Code:**
In the original code example, there’s a violation of the
Separation of Concerns principle. The `ShoppingCart class`
is responsible for both maintaining the list of products and
calculating the total cost. Here’s what’s problematic:
- **Mixed Responsibilities:- The ShoppingCart class**
has mixed responsibilities. It’s managing the shopping cart
(product list) and performing complex calculations, including tax calculation.
- This mixing of responsibilities makes the codebase
Listing 1.
```
1. products[] = $product;
16. }
17.
18. public function calculateTotal() {
19. $total = 0;
20. foreach ($this->products as $product) {
21. $category = $product->category;
22. if ($category === 'Electronics') {
23. // 10% tax for electronics
24. $total += $product->price * 1.1;
25. } elseif ($category === 'Clothing') {
26. // 20% tax for clothing
27. $total += $product->price * 1.2;
28. } else {
29. $total += $product->price;
30. }
31. }
32. return $total;
33. }
34. }
35.
36. $cart = new ShoppingCart();
37. $cart->addProduct('Laptop', 1000, 'Electronics');
38. $cart->addProduct('Shirt', 20, 'Clothing');
39. $cart->addProduct('Phone', 500, 'Electronics');
40. $cart->addProduct('Jeans', 50, 'Clothing');
41.
42. $total = $cart->calculateTotal();
43. echo "Total cost: $" . $total;
```
-----
###### Is Your Code Abstracted Enough To Minimize Load?
harder to understand and maintain.
- **Limited Reusability:- The tax calculation logic is**
tightly coupled to the ShoppingCart class, making it challenging to reuse in other parts of the application.
- **Maintenance Challenges:- Any changes or updates**
to tax rules or product categories would require modifications in the ShoppingCart class, potentially introducing errors
or unexpected behavior.
Listing 2 shows how we can restructure the code to adhere
to the SoC principle.
Listing 2.
```
1. name = $name;
11. $this->price = $price;
12. $this->category = $category;
13. }
14. }
15.
16. class Category {
17. public $name;
18. public $taxRate;
19.
20. public function __construct($name, $taxRate) {
21. $this->name = $name;
22. $this->taxRate = $taxRate;
23. }
24. }
25.
26. class TaxCalculator {
27. public static function calculateTax(
28. $product, $categories
29. ) {
30. foreach ($categories as $category) {
31. if ($category->name === $product->category) {
32. return $product->price * $category->taxRate;
33. }
34. }
35. return 0; // No tax if category not found
36. }
37. }
38.
```
Listing 2 continues.
```
39. class ShoppingCart {
40. public $products = [];
41. public $categories = [];
42.
43. public function addProduct($name, $price, $cat) {
44. $product = new Product($name, $price, $cat);
45. $this->products[] = $product;
46. }
47.
48. public function addCategory($name, $taxRate) {
49. $category = new Category($name, $taxRate);
50. $this->categories[] = $category;
51. }
52.
53. public function calculateTotal() {
54. $total = 0;
55. foreach ($this->products as $product) {
56. $tax = TaxCalculator::calculateTax(
57. $product,
58. $this->categories
59. );
60. $total += $product->price + $tax;
61. }
62. return $total;
63. }
64. }
65.
66. $cart = new ShoppingCart();
67. $cart->addProduct('Laptop', 1000, 'Electronics');
68. $cart->addProduct('Shirt', 20, 'Clothing');
69. $cart->addProduct('Phone', 500, 'Electronics');
70. $cart->addProduct('Jeans', 50, 'Clothing');
71.
72. // 10% tax for Electronics
73. $cart->addCategory('Electronics', 0.1);
74. // 20% tax for Clothing
75. $cart->addCategory('Clothing', 0.2);
76.
77. $total = $cart->calculateTotal();
78. echo "Total cost: $" . $total;
```
**Explanation of Using Category Class:**
- **Introduction of the Category Class:- We’ve intro-**
duced a Category class that encapsulates the category name
and its corresponding tax rate.
- **Category Management:- The** `ShoppingCart class`
now maintains a list of categories, allowing you to add and
manage categories with their respective tax rates.
- **Tax Calculation with Categories:- The** `TaxCal-`
```
culator class uses the list of categories to find the tax rate
```
-----
###### Is Your Code Abstracted Enough To Minimize Load?
associated with a product’s category.
- This eliminates the need for conditional logic and
provides a cleaner, more maintainable approach.
- **Improved Extensibility:- Adding new prod-**
uct categories and tax rates is straightforward by using the
```
addCategory method in the ShoppingCart class.
```
By adopting a `Category class to manage tax rates for`
different product categories, you’ve abstracted the tax calculation logic in a clean and extensible manner, adhering to the
principles of Separation of Concerns and making your code
more readable and maintainable.
So we’re already headed in the right direction, but I’m sure
we can do more:
###### A Fully Abstracted Example
Listing 3 on the next page shows the full abstraction with
the following key abstraction concepts:
- **Interface for Tax Calculation:- We define a**
```
TaxCalculator interface, allowing for various tax calculation
```
strategies.
- **Category-Specific Tax Calculation:- Each Category**
now has its own tax calculator, which can be easily customized by implementing the TaxCalculator interface.
- **Encapsulation:- The tax calculation logic is encap-**
sulated within each category, making it easy to swap tax
calculation methods as needed.
- **Usage of Anonymous Classes:- Anonymous classes**
are used for inline tax calculator implementations, promoting a highly abstracted and flexible approach.
By taking code abstraction to the next level, this example
promotes greater flexibility, extensibility, and maintainability
while still adhering to clean coding principles. It allows for
easily adding new categories and customizing tax calculation
strategies without affecting the core logic.
###### Another Example
We’ll look at a simple content management system (CMS)
that handles articles. We’ll start with a badly abstracted code
example shown in Listing 4, explain why it’s problematic, and
then provide a fully abstracted and improved version.
**Issues with the Badly Abstracted Code:**
- **Lack of Abstraction:- The** `ArticleManager class`
directly deals with the creation, retrieval, and deletion of articles, which is a violation of the Single Responsibility Principle.
- **Limited Extensibility:- The code is not designed to**
easily accommodate future changes or additional functionality. For example, if you want to add tags or categories to
articles, the code becomes unwieldy.
- **Tight Coupling:- The code is tightly coupled,**
making it challenging to substitute or extend components.
Any change requires modifications throughout the class.
Listing 4.
```
1. class Article {
2. public $title;
3. public $content;
4.
5. public function __construct($title, $content) {
6. $this->title = $title;
7. $this->content = $content;
8. }
9. }
10.
11. class ArticleManager {
12. private $articles = [];
13.
14. public function addArticle($title, $content) {
15. $article = new Article($title, $content);
16. $this->articles[] = $article;
17. }
18.
19. public function findArticleByTitle($title) {
20. foreach ($this->articles as $article) {
21. if ($article->title === $title) {
22. return $article;
23. }
24. }
25. return null;
26. }
27.
28. public function deleteArticle($title) {
29. foreach ($this->articles as $key => $article) {
30. if ($article->title === $title) {
31. unset($this->articles[$key]);
32. }
33. }
34. }
35. }
36.
37. $articleManager = new ArticleManager();
38. $articleManager->addArticle('First Article',
39. 'This is the content of the first article.');
40. $articleManager->addArticle('Second Article',
41. 'Content of the second article.');
42.
43. $foundArticle = $articleManager->findArticleByTitle(
44. 'First Article');
45. if ($foundArticle) {
46. echo "Found article: {$foundArticle->title}";
47. }
48.
49. $articleManager->deleteArticle('First Article');
```
-----
###### Is Your Code Abstracted Enough To Minimize Load?
Listing 3.
```
1. name = $name;
21. $this->price = $price;
22. $this->category = $cat;
23. }
24. }
25.
26. class Category {
27. public function __construct(
28. public string $name,
29. private readonly TaxCalculator $taxCalculator
30. ) { }
31.
32. public function calculateTax(
33. Product $prod
34. ): float {
35. return $this->taxCalculator->calculateTax($prod);
36. }
37. }
38.
39. class ShoppingCart {
40. private $products = [];
41.
42. public function addProduct(Product $product) {
43. $this->products[] = $product;
44. }
45.
46. public function calculateTotal(): float {
47. $total = 0;
48. foreach ($this->products as $product) {
49. $total += $product->price +
50. $product->category->calculateTax($product);
51. }
52. return $total;
53. }
54. }
```
Listing 3 continued.
```
55.
56. // Usage
57. $cart = new ShoppingCart();
58.
59. $electronicsCategory = new Category(
60. 'Electronics',
61. new class implements TaxCalculator {
62. public function calculateTax(
63. Product $product
64. ): float {
65. return $product->price * 0.1; // 10% tax
66. }
67. });
68.
69. $clothingCategory = new Category(
70. 'Clothing',
71. new class implements TaxCalculator {
72. public function calculateTax(
73. Product $product
74. ): float {
75. return $product->price * 0.2; // 20% tax
76. }
77. });
78.
79. $product1 = new Product(
80. 'Laptop', 1000, $electronicsCategory
81. );
82. $product2 = new Product(
83. 'Shirt', 20, $clothingCategory
84. );
85. $product3 = new Product(
86. 'Phone', 500, $electronicsCategory
87. );
88. $product4 = new Product(
89. 'Jeans', 50, $clothingCategory
90. );
91.
92. $cart->addProduct($product1);
93. $cart->addProduct($product2);
94. $cart->addProduct($product3);
95. $cart->addProduct($product4);
96.
97. $total = $cart->calculateTotal();
98. echo "Total cost: $" . $total;
```
-----
###### Is Your Code Abstracted Enough To Minimize Load?
Listing 5 shows a fully abstracted and improved version:
**Explanation of the Improved Code:**
- **Separation of Concerns:- We’ve separated the Arti-**
```
cle class to represent an article’s structure and data.
```
- The `ArticleRepository class is responsible for`
managing articles, adhering to the Single Responsibility Principle.
- **Encapsulation and Dependency Injection:- The**
```
Article class encapsulates its data and provides methods for
```
retrieval, promoting data privacy.
- Dependency injection is used to pass Article objects
into the ArticleRepository.
- **Flexibility and Extensibility:- This code is more**
adaptable to future changes. Adding features like tags, categories, or improved searching can be done without major
modifications.
- **Reduced Coupling:- Components are loosely**
coupled, making it easier to replace or extend individual
parts of the code.
In this improved code, we’ve achieved a high level of
abstraction, adhering to the principles of clean coding,
making it more maintainable, extensible, and adaptable to
future requirements. The code focuses on a single responsibility for each class, ensuring better organization and ease of
development.
Let’s take the abstraction to an even higher level. In Listing
6 on the next page, we’ll further abstract the code by introducing an abstract `Content class that can represent various`
types of content, not just articles. We’ll also employ a repository pattern for better data management and make use of
interfaces for extensibility.
**Explanation of the High-Level Abstraction:**
- **Content Interface:- We’ve introduced a** `Content`
interface that defines methods for getting the title and content.
This abstracts content to be more than just articles.
- **Extensible Content Types:- We’ve added a** `Video`
class that implements the Content interface, showing how the
code can easily accommodate different content types.
- **Generic Content Repository:- We’ve created a**
```
ContentRepository interface and an implementation ContentRepositoryImpl, which can handle various types of content
```
(articles, videos, etc.) through the Content interface.
- **Reduced Coupling and Enhanced Flexibility:- The**
code is highly abstracted, making it easy to add new content
types or features without major modifications.
This high-level abstraction allows for managing various
types of content with minimal changes to the code, adhering
to clean coding principles. It promotes flexibility, extensibility,
and reduced coupling while maintaining a clean and organized code structure.
Listing 5.
```
1. class Article {
2. private $title;
3. private $content;
4.
5. public function __construct($title, $content) {
6. $this->title = $title;
7. $this->content = $content;
8. }
9.
10. public function getTitle() {
11. return $this->title;
12. }
13.
14. public function getContent() {
15. return $this->content;
16. }
17. }
18.
19. class ArticleRepository {
20. private $articles = [];
21.
22. public function addArticle(Article $article) {
23. $this->articles[] = $article;
24. }
25.
26. public function findArticleByTitle($title) {
27. foreach ($this->articles as $article) {
28. if ($article->getTitle() === $title) {
29. return $article;
30. }
31. }
32. return null;
33. }
34.
35. public function deleteArticle(Article $article) {
36. $articles = $this->articles;
37. foreach ($articles as $key => $storedArticle) {
38. if ($storedArticle === $article) {
39. unset($this->articles[$key]);
40. }
41. }
42. }
43. }
44.
45. $articleRepository = new ArticleRepository();
46. $article1 = new Article('First Article',
47. 'This is the content of the first article.');
48. $article2 = new Article('Second Article',
49. 'Content of the second article.');
50.
51. $articleRepository->addArticle($article1);
52. $articleRepository->addArticle($article2);
53.
54. $foundArticle = $articleRepository
55. ->findArticleByTitle('First Article');
56. if ($foundArticle) {
57. echo "Found article: {$foundArticle->getTitle()}";
58. }
59.
60. $articleRepository->deleteArticle($article1);
```
-----
###### Is Your Code Abstracted Enough To Minimize Load?
Listing 6.
```
1. interface Content {
2. public function getTitle(): string;
3. public function getContent(): string;
4. }
5.
6. class Article implements Content {
7. private $title;
8. private $content;
9.
10. public function __construct($title, $content) {
11. $this->title = $title;
12. $this->content = $content;
13. }
14.
15. public function getTitle(): string {
16. return $this->title;
17. }
18.
19. public function getContent(): string {
20. return $this->content;
21. }
22. }
23.
24. class Video implements Content {
25. private $title;
26. private $url;
27.
28. public function __construct($title, $url) {
29. $this->title = $title;
30. $this->url = $url;
31. }
32.
33. public function getTitle(): string {
34. return $this->title;
35. }
36.
37. public function getContent(): string {
38. return "";
39. }
40. }
41.
42. interface ContentRepository {
43. public function addContent(Content $content);
44. public function findContentByTitle(
45. string $title
46. ): ?Content;
47. public function deleteContent(Content $content);
48. }
49.
```
Listing 6 continued.
```
50. class ContentRepositoryImpl
51. implements ContentRepository {
52. private $contents = [];
53.
54. public function addContent(Content $content) {
55. $this->contents[] = $content;
56. }
57.
58. public function findContentByTitle(
59. string $title
60. ): ?Content {
61. foreach ($this->contents as $content) {
62. if ($content->getTitle() === $title) {
63. return $content;
64. }
65. }
66. return null;
67. }
68.
69. public function deleteContent(Content $content) {
70. $contents = $this->contents;
71. foreach ($contents as $key => $storedContent) {
72. if ($storedContent === $content) {
73. unset($this->contents[$key]);
74. }
75. }
76. }
77. }
78.
79. $repository = new ContentRepositoryImpl();
80.
81. $article = new Article('First Article',
82. 'This is the content of the first article.');
83. $video = new Video('Intro Video',
84. 'https://www.youtube.com/embed/12345');
85.
86. $repository->addContent($article);
87. $repository->addContent($video);
88.
89. $foundContent = $repository->findContentByTitle(
90. 'Intro Video'
91. );
92. if ($foundContent) {
93. echo "Found: {$foundContent->getTitle()}\n";
94. echo "Display:\n{$foundContent->getContent()}";
95. }
96.
97. $repository->deleteContent($article);
```
-----
###### Is Your Code Abstracted Enough To Minimize Load?
###### The Risks of Over Abstraction
Let’s first provide a badly abstracted code example showing
in Listing 7. After that, we’ll delve into an over-abstracted
code example, highlighting the potential risks associated with
excessive abstraction.
Listing 7.
```
1. name = $name;
8. $this->salary = $salary;
9. }
10. }
11.
12. class Payroll {
13. public $employees = [];
14.
15. public function addEmployee($name, $salary) {
16. $employee = new Employee($name, $salary);
17. $this->employees[] = $employee;
18. }
19.
20. public function calculateTotalSalary() {
21. $total = 0;
22. foreach ($this->employees as $employee) {
23. $total += $employee->salary;
24. }
25. return $total;
26. }
27. }
28.
29. $payroll = new Payroll();
30. $payroll->addEmployee('John', 50000);
31. $payroll->addEmployee('Alice', 60000);
32.
33. $totalSalary = $payroll->calculateTotalSalary();
34. echo "Total salary: $totalSalary";
```
**Issues with Badly Abstracted Code:**
- **Lack of Separation of Concerns:- The Payroll**
class is responsible for both managing employee data and
performing salary calculations, which violates the Single
Responsibility Principle.
- **Low Reusability and Flexibility:- The code is not**
easily reusable for different salary calculation methods or for
handling other aspects of employee management.
- **Tight Coupling:- The code is tightly coupled,**
making it challenging to adapt to changes or different employee data structures.
Now, let’s explore an example of over-abstracted code and
highlight the potential risks shown in Listing 8 on the next
page.
**Risks of Over-Abstracted Code:**
- **Complexity:- The code introduces unnecessary**
complexity with multiple classes and interfaces. In this
example, managing employees and their salaries is more
complicated than needed.
- **Reduced Readability:- Over-abstraction can make**
the code harder to read and understand, especially for simple
tasks like calculating salaries.
- **Maintenance Challenges:- Excessive abstraction**
can result in code that is difficult to maintain and modify. For
example, adding a new type of employee may require changes
across multiple classes.
- **Decreased Development Speed:- Over-abstracted**
code often results in slower development because developers need to navigate complex structures for relatively simple
tasks.
In this over-abstracted code, we can observe that while it
adheres to abstraction principles, it introduces unnecessary
complexity and potential maintenance challenges. It’s crucial
to strike a balance between abstraction and simplicity to
achieve clean and maintainable code.
###### Striking a Balance
Balancing abstraction and simplicity is crucial to writing
clean and maintainable code. The goal is to abstract where
it adds value, making the code more flexible and understandable without overcomplicating it. Here are some key
principles to achieve this balance:
- **Single Responsibility Principle (SRP): Each class or**
module should have one and only one reason to change.
When abstracting, ensure that each component is
responsible for a single aspect of the application.
- **Keep It Simple: Start with the simplest solution that**
works and only add complexity when necessary. Avoid
over-engineering by considering the current requirements and potential future changes.
- **Clear and Intuitive Abstractions: Make abstractions**
that are intuitive and easy to understand. A well-abstracted codebase should make the code more readable,
not less.
- **Use Interfaces Sparingly: While interfaces can be**
powerful tools for abstraction, don’t create interfaces
for every class. Use them when you need to define a
contract that multiple classes should adhere to.
- **Abstraction Levels: Consider different levels of**
abstraction. High-level abstractions are suitable for
complex components, while low-level abstractions work
well for small, reusable functions or objects.
- **YAGNI (You Ain’t Gonna Need It): Don’t abstract or**
add complexity based on hypothetical future requirements. Abstraction should be driven by present needs,
not speculative ones.
-----
###### Is Your Code Abstracted Enough To Minimize Load?
Listing 8.
```
1. interface Employee {
2. public function getName(): string;
3. public function getSalary(): float;
4. }
5.
6. class RegularEmployee implements Employee {
7. private $name;
8. private $salary;
9.
10. public function __construct($name, $salary) {
11. $this->name = $name;
12. $this->salary = $salary;
13. }
14.
15. public function getName(): string {
16. return $this->name;
17. }
18.
19. public function getSalary(): float {
20. return $this->salary;
21. }
22. }
23.
24. class ContractorEmployee implements Employee {
25. private $name;
26. private $hourlyRate;
27. private $hoursWorked;
28.
29. public function __construct(
30. $name, $hourlyRate, $hoursWorked
31. ) {
32. $this->name = $name;
33. $this->hourlyRate = $hourlyRate;
34. $this->hoursWorked = $hoursWorked;
35. }
36.
37. public function getName(): string {
38. return $this->name;
39. }
40.
41. public function getSalary(): float {
42. return $this->hourlyRate * $this->hoursWorked;
43. }
44. }
```
Listing 8 continued.
```
45.
46. class EmployeeRepository {
47. private $employees = [];
48.
49. public function addEmployee(Employee $employee) {
50. $this->employees[] = $employee;
51. }
52.
53. public function calculateTotalSalary() {
54. $total = 0;
55. foreach ($this->employees as $employee) {
56. $total += $employee->getSalary();
57. }
58. return $total;
59. }
60. }
61.
62. $employeeRepository = new EmployeeRepository();
63. $employeeRepository->addEmployee(
64. new RegularEmployee('John', 50000)
65. );
66. $employeeRepository->addEmployee(
67. new ContractorEmployee('Alice', 30, 160)
68. );
69.
70. $salary = $employeeRepository->calculateTotalSalary();
71. echo "Total salary: $salary";
```
-----
###### Is Your Code Abstracted Enough To Minimize Load?
- **Code Reviews and Refactoring: Regularly review**
and refactor your code. Over time, your understanding
of what should be abstracted may change. Refactoring
helps to maintain a good balance.
- **Real-world Testing: Apply abstractions when they**
are tested in real-world scenarios. Code abstractions
are valuable when they make testing and maintenance
easier.
- **Documentation: Document your abstractions and**
the reasoning behind them. This helps other developers understand why a specific level of abstraction was
chosen.
- **Pragmatism: Be pragmatic. There is no one-size-fits-**
all answer. The right level of abstraction depends on the
context, the size and complexity of the project, and the
team’s familiarity with the codebase.
Balancing abstraction and simplicity is an ongoing process.
It’s about finding the right level of abstraction that solves the
current problem efficiently while allowing for future changes
with minimal disruption. Regularly revisit your code to
ensure that your abstractions remain relevant and aligned
with your project’s needs.
Next month, we’re delving into our next question—“Is Your
Code Encapsulated Enough To Be Clear?”. You’ll have to wait
until next month to find out how, but Encapsulation and
Abstraction are not the same thing!
_In 1983, Christopher was introduced to_
_computers by his dad, at the tender age_
_of 3. now, over 40 years later, he has been_
_working in the industry for over 20 years_
_making an impact across multiple sectors of_
_the industry. Starting with launching the first_
_web development company in Staffordshire,_
_Christopher dealt with the web - when the_
_web was little more than just pretty text. He_
_established the websites for many different_
_businesses in their first inception, before_
_moving onto web applications a little while_
_later. Illness prevented Christopher from_
_working in the industry full time for some_
_considerable time - but recovery meant he_
_could tackle once again the joys of code - but_
_he soon found that his skills had become_
_out of date, so thanks to the School of Code_
_in the UK he was able to return to the_
_workplace with revitalised skills ready to_
_tackle the next wave. Specialising since the_
_School of Code in Readable Code, He has_
_worked with a large number of languages,_
_specialising in supporting businesses to grow_
_standards for their code base, and now he is_
_ready to share his processes with the world._
_[@ccmiller2018](https://twitter.com/ccmiller2018)_
-----
### Gratefully Looking Back
###### Beth Tucker Long
I was recently talking to someone who had no idea what I did. I explained a bit about the programming I did, and even though they are in a completely different field, they had heard just enough about it to know that JavaScript was a thing. Did I work with that? Yes, a bit, but mostly I work with PHP. Oh, PHP is a really old language, isn’t it?
That is a tough question. After all, “old” is such a relative
term. According to my kids, anything from the “late 1900’s”
is “super old”. Then again, compared to COBOL, Fortran, or
even C, PHP is pretty young. Either way, it is definitely one
of the more established web programming languages of our
time.
PHP has come a long way since it was first created as a
hobby project to solve a specific problem. This is a testament
to the dedicated community around it. Despite being almost
entirely run by volunteers, the PHP infrastructure, much like
the language itself, has only gotten more organized and more
efficient over time. Releases are clearly scheduled, well-documented, and given a set roadmap so that everyone knows
how long any given release will be maintained. This all allows
developers to rely on the stability of PHP while knowing
when upgrades will need to take place to stay running on a
fully-supported version.
The PHP Foundation has taken this a step further, adding
in more stability and security to the developers working on
PHP’s core. Again, started by and supported by PHP community members (there are almost 200 organizations and over
1000 individuals supporting the PHP Foundation), the PHP
Foundation organized a way to provide paid development
time to bring consistency to internal development and make
sure that we are not relying on too few people to know and
manage internals. This was such a critical and influential step
because even though the PHP community is an amazing
group of volunteers, it is unreasonable to expect volunteers
to keep such a heavily-relied-upon system running in their
free-time. The creation of a foundation acknowledges the
immense value of contributions to PHP’s core and seeks to
ensure that those contributions keep coming.
With the release of 8.3 rapidly approaching, take a moment
to not only look at the new features PHP is gaining but also
how far it has come. It’s hard to believe this started as one
person trying to code less and now supports a worldwide
industry of people coding more and more amazing things.
To everyone who has contributed to PHP, whether it be tests,
code, or even financially through the PHP Foundation, you
are all so appreciated. Thank you!
###### Related URLs:
- Donate to The PHP Foundation: https://thephp.foundation/donate/[1]
- Interview with Rasmus Lerdorf on the beginnings of
PHP: https://phpa.me/webarchive-conversationsnetwork[2]
- PHP 8.3: https://wiki.php.net/todo/php83[3]
_Beth Tucker Long is a developer and owner_
_at Treeline Design, a web development_
_company, and runs Exploricon, a gaming_
_convention, along with her husband, Chris._
_She leads the Madison Web Design & Devel-_
_opment and Full Stack Madison user groups._
_You can find her on her blog (http://www._
_[alittleofboth.com) or on Twitter @e3BethT](https://twitter.com/e3BethT)_
_1_ _[https://thephp.foundation/donate/](https://thephp.foundation/donate/)_
_2_ _[https://phpa.me/webarchive-conversationsnetwork](https://phpa.me/webarchive-conversationsnetwork)_
_3_ _[https://wiki.php.net/todo/php83](https://wiki.php.net/todo/php83)_
-----