Stefan

Introduction

Welcome to another exciting issue of The PHP Teacher Magazine! This month, we’re diving deep into a variety of topics, from state management and application architecture to security best practices and the latest developments in PHP. Whether you’re a seasoned developer or just starting out, we’ve got something for everyone. This issue includes articles about finite-state machines, Vim plugins, single-page applications, cybersecurity, PHP releases, creating a webserver, application events, number formatting, and Laracon.

We’ll explore how to build robust and maintainable applications, improve our workflows with powerful tools, and stay ahead of the curve with the latest PHP features. Get ready to enhance your skills and broaden your understanding of the PHP landscape. Let’s jump in!

Articles

Finite-state Machines with PHP 8.1

By Scott Keck-Warren

Managing the state of entities in our applications can often become a complex and error-prone task. This article explores how to leverage finite-state machines (FSMs) to manage these flows more effectively using PHP 8.1’s features.

An FSM is an abstract model that can be in only one state at a time from a finite set of states. The FSM transitions from one state to another based on input. When defining an FSM, you must specify the list of states, the initial state, and the transitions. A traffic light, for instance, can be modelled as an FSM with states like Off, Red, Yellow, and Green. The transitions would be off to red (on boot), red to green (after x seconds), green to yellow (after y seconds) and yellow to red (after z seconds).

     +-----+    boot    +-----+   X secs  +-----+   Y secs  +-----+   Z secs  +-----+
     | Off |----------->| Red |--------->|Green|--------->|Yellow|--------->| Red |
     +-----+             +-----+          +-----+          +-----+            +-----+

A good use case of FSMs in web applications is managing the states of a blog post in its publishing flow. The states could be Draft, Ready for Review, Scheduled, and Published. Using PHP 8.1’s enumerations (enums), you can define these states as valid values, much like a class. Enums enhance type safety, preventing invalid state assignments. For example:

enum PublishedState {
    case Draft;
    case ReadyForReview;
    case Scheduled;
    case Published;
}

Without enums, you might use class constants to keep track of states. However, using enums provides better type safety. With enums, you can enforce that function parameters are of the correct state, and prevent invalid transitions. For example, instead of using a string $newState as the parameter, you can require the PublishedState enum:

function updateState(PublishedState $newState): void {
//...
}

To represent states outside of enums, you can use backed enums with scalar values. You can associate strings with each case in your enum:

enum PublishedState: string {
    case Draft = "Draft";
    case ReadyForReview = "Ready For Review";
    case Scheduled = "Scheduled";
    case Published = "Published";
}

PHP prevents you from duplicating values when using backed enums or from skipping values. The value property can then be used to retrieve the associated scalar value. The from() method converts a scalar value back to its enum value:

$state = PublishedState::from("Scheduled");
var_dump($state); // enum(PublishedState::Scheduled)

The tryFrom() function provides a safer way to get the enum from scalar value by returning null instead of throwing an error. Enums can also contain methods, which makes them ideal for validating transitions. The isValidTransition function in listing 4 is an example of validating the transitions for the PublishedState enum:

enum PublishedState:string {
    case Draft = "Draft";
    case ReadyForReview = "Ready For Review";
    case Scheduled = "Scheduled";
    case Published = "Published";

    function isValidTransition(PublishedState $newState) {
        $transitions = [
            PublishedState::Draft->value => [
                PublishedState::ReadyForReview,
            ],
            PublishedState::ReadyForReview->value => [
                PublishedState::Draft,
                PublishedState::Scheduled,
            ],
            PublishedState::Scheduled->value => [
                PublishedState::Published
            ],
        ];
        return in_array($newState, $transitions[$this->value]);
    }
}

With this, you can ensure that the blog post only moves from one valid state to the next. Finite-state machines provide a structured approach to managing state in applications and combined with the power of enums you can create well defined business logic.

Universal Vim Part Three: Putting the You in Utility

By Andrew Woods

This article completes the series on crafting a universal Vim experience by introducing several plugins to enhance speed, agility, and efficacy. We will explore several Vim plugins and how they can improve your workflow.

Startify

This plugin provides a menu when you start Vim without arguments, listing recently changed files and sessions. It allows you to quickly get back to your previous work, and it is configurable to display relevant information for the user. Startify lists the items in each section with a single character box next to each item. You can move the cursor and press enter or type the character to open an item. The item can be opened in a split (horizontal or vertical) or a tab using the s, v, or t keys. Startify handles sessions in the ~/.vim/sessions folder, and also offers functions to manage sessions:

:SLoad load a session
:SSave save a session
:SDelete delete a session
:SClose close current session

Startify helps you begin your Vim session with ease.

  _
 /   \
| (oo) |
 \  -- /
  || ||
  ~~ ~~
Undo Tree

This plugin exposes Vim’s change history as a tree, allowing you to navigate back through your history. You can use :UndotreeToggle to access your change history and a diff of each change. This allows you to recover content you have edited. Undotree supports branches of changes allowing you to navigate back to an alternative state of the file. You can also customize the layout of the plugin to fit your needs.

      +----------------+
      |   Main Branch  |
      +----------------+
          /         \
    +-------+   +--------+
    |Branch 1|   |Branch 2|
    +-------+   +--------+
Vim Surround

This plugin makes it easy to add, change, or remove quotes from strings, tags, parentheses and brackets, and braces. For example, if you have the text Eric and John bought PHP Architect from Oscar, you can put single quotes around PHP Architect by putting the cursor anywhere on the word PHP and typing ys2aw'. To change the single quotes to double quotes, you can type cs'". To remove the quotes you can type ds". Vim Surround supports visual selection. For example, if you select the text PHP Architect by typing viwe and then type S' the selected text will be enclosed in single quotes.

        ____________________
       /        Text       /
      /  Add, Change, Remove/
     /   Quotes, Tags, More/
    /___________________/
Ultisnips

This plugin is a text expander for Vim that allows you to trigger snippets with a few keystrokes. You can add placeholders in your snippets to inject values, allowing for more complex text expansions than Vim’s native abbreviations. This plugin is useful for writing code and even for more generic tasks, like emails. UltiSnips is also used for multi-line expansions. Snippets are organized by file type, but can also be grouped in a file called all.snippets to transcend file types.

  +----------+       +----------+
  |   Code   |  -->  | Expanded |
  | Snippet  |       |   Text   |
  +----------+       +----------+
Lightline

This plugin is an improved status bar. Lightline provides more information about the file you’re currently editing. The settings are kept in the g:lightline variable. It allows for easy customization and integrates with your color scheme.

+---------------------------------------+
| File: example.php  Line: 12  Col: 35 |
+---------------------------------------+

These plugins, among others, will enhance your Vim experience making it more efficient and easier to use.

Cheating at SPA with Breeze & Inertia

By Joe Ferguson

This article explores how to quickly build single-page applications (SPAs) using Laravel Breeze, Inertia, and Vue.js. Inertia is a JavaScript library that allows building server-side rendered pages with JavaScript components. It provides the experience of a client-side SPA without building a dedicated API.

To start you create a new Laravel project and install Breeze:

$ composer create-project laravel/laravel inertia
$ cd inertia/
$ composer require laravel/breeze --dev
$ php artisan migrate
$ php artisan breeze:install vue
$ npm run dev

Breeze gives you a registration form along with other user functionality. The default route uses Inertia::render to render a Vue.js template instead of a Blade template. The /dashboard route is served by resources/js/Pages/Dashboard.vue.

   Laravel
     |
     | Inertia
     v
   Vue.js

Breeze provides layouts for authenticated users, AuthenticatedLayout, and unauthenticated users, GuestLayout. The application’s components are located in the resources/js/Components and resources/js/Pages folders.

You can display tasks using a Task component within an Index.vue file using a v-for loop and pass data as props to the child component:

<template>
    <div v-for="task in tasks" :key="task.id" :task="task" />
</template>

<script setup>
    // code ...
</script>

The setup section contains the JavaScript logic and the template section displays the information. The data comes from the controller and is passed to the frontend using defineProps. For example:

<script setup>
    const props = defineProps(['task']);
</script>

A Task model is used to store tasks, and you can use artisan to create this model, the migration, and a resource controller. The command php artisan make:model --migration --resource --controller Task does this in one step. The resource controller methods can be used to manage database tasks. The Route::resource configuration handles the routes. You can then apply the auth and verified middleware to the routes.

A policy can be created that limits editing and deletion access to the task owner using php artisan make:policy TaskPolicy --model=Task. In the policy update and delete methods can be overridden to allow only the owner of the task to edit it. The Task controller uses these policies to authorize access to the methods:

    public function update(Request $request, Task $task)
    {
        $this->authorize('update', $task);
        //...
    }

Inertia handles redirects, and refreshes the relevant content. This allows you to edit and delete tasks without refreshing the entire page. With Inertia, you can build powerful single-page applications without the complexity of APIs. You can add, edit, and delete tasks by building a basic CRUD application while relying heavily on the existing layout provided by Breeze.

Cybersecurity Checkup

By Eric Mann

This article provides four actions you can take to enhance your security posture for Cybersecurity Awareness Month.

  1. Enable Multi-Factor Authentication (MFA): MFA protects against password breaches by requiring an additional factor beyond your password. Time-based one-time passwords are an easy and secure form of MFA. Use authenticator applications like Google Authenticator or Authy. Physical keys like YubiKey are even more secure. Avoid using SMS-based codes for authentication.
  +---------+       +----------+      +---------+
  |  User   |  -->  |Password   |  -->|  Second |
  |         |       |   + MFA  |      |  Factor |
  +---------+       +----------+      +---------+
  1. Build Strong Passwords: A strong password should contain many characters, and it should not use dictionary words. Entropy makes a password secure, not complexity. A passphrase made of memorable dictionary words is more secure than a complex password. You can use password managers like 1Password to generate and store truly secure passwords.
     +------------------------+
     |  Password Complexity  |
     |      vs Entropy       |
     +------------------------+
  1. Recognize and Report Phishing: Phishing attacks are often the start of a cyber attack. These emails impersonate someone you know to trick you into downloading malicious software. Look for the following to identify a phishing email: Does the sender know you personally, is the email asking you to do something out of the ordinary, is there a sense of urgency. Do not follow links embedded in suspicious emails. Contact the sender to confirm, and always report suspicious emails.

          +--------------------+
          | Identify Phishing  |
          |  - Sender          |
          |  - Requests        |
          |  - Urgency         |
          +--------------------+
  2. Update Your Software: Outdated software is a major security risk. Update your systems and endpoints regularly to avoid vulnerabilities. Apple recently released a patch for a remote code execution flaw in the Webkit rendering engine. Install software updates and reboot your computer to ensure you are using the latest patches.

     +-------------+
     | Software    |
     | Updates     |
     |  - Regular  |
     |  - Critical |
     +-------------+

By implementing these actions, you can significantly improve your cybersecurity and protect your digital assets.

#### New and Noteworthy

This section covers some of the recent announcements and updates in the PHP community.

*   **PHP Releases:**
 *   PHP 8.2.0 (RC 3) is available for testing.
 *   PHP 8.1.11 and PHP 8.0.24 have been released.

*   **php[tek] is Returning:** The php[tek] conference is returning to Chicago in 2023 and is seeking sponsors.

*   **Asymmetric Visibility:** This concept allows class properties to be publicly read-only while being set privately or protectedly.

*   **Import Laravel Vapor DNS to Cloudflare:** The Cumulus package helps manage DNS records when using Cloudflare.

*   **Learn PestPHP From Scratch:** A free video course from Laracasts teaches how to use Pest PHP.

*   **RFC: `json_validate()`:** A proposal to add a `json_validate()` function to verify if a string contains valid JSON.

*   **RFC: Improve `unserialize()` error handling:**  A new `UnserializationFailedException` will be thrown when unserialization fails.

*   **RFC: StreamWrapper Support for `glob()`:** Implementing StreamWrappers support for the `glob()` function.

*  **RFC: Deprecations for PHP 8.3**: An umbrella RFC that lists features to be considered for deprecation in PHP 8.3 and removal in PHP 9.

These updates highlight the ongoing evolution of the PHP ecosystem and the efforts to make the language more powerful and secure.

+-------------------+ | PHP Development | | - New Features | | - Performance | | - Security | +-------------------+


#### Making Our Own Web Server: Part 1

By Chris Tankersley

This article discusses how PHP applications differ from other "modern" web languages, and it shows how PHP can process its own requests without a web server. Unlike languages like Node.js, PHP is not a server; instead, it relies on a web server to handle HTTP connections. The typical life cycle of a PHP script is "start, process, die". PHP expects an external server, like Apache's `httpd` or PHP-FPM, to handle requests.

+----------+ +----------+ +----------+ | Client | —> | Web | —> | PHP | | | | Server | —> | Engine | +----------+ +----------+ +----------+


The most common setups for PHP involve using the PHP engine as a module inside Apache's `httpd` web server, or as proxied FastCGI processes using PHP's FPM manager. `httpd` starts a PHP engine in each of its own processes, and when a web request comes in, `httpd` processes the request and then pipes it through the PHP engine. In a FastCGI setup the web server (like nginx) hands the request off to the PHP-FPM, which spawns a number of PHP processes. Regardless of the setup, PHP processes a single request at a time and relies on the web server to return the response.

PHP has a built-in development server which is a very basic, single-threaded web server that can be used to run an application without the need for a full web server, starting with PHP 5.4.0. You can start the built in server using the command line `php -S [:port] [-t /docroot] [/router.php]`. Starting with PHP 7.4 it's possible to run a multi-threaded development server, but it is not recommended for production. While convenient for development, the built in server is not production-ready and has downsides such as only handling a single connection, and lower performance when serving static files.

PHP has a number of lower level C functions which are exposed as PHP methods. One of them deals with network sockets. Using these you can open a listening socket that waits for connections, then it reads data and sends responses. The following functions are used to build this socket server:

*   `socket_create` - Creates a socket
*   `socket_bind` - Binds the socket to an IP address and port
*   `socket_listen` - Start listening on the socket
*   `socket_accept` - Start accepting connections
*   `socket_read` - Read information from the socket
*  `socket_write` - Write information back to the socket
*   `socket_close` - Close the socket

Here is some example code of using these to create a basic socket server:

```php
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($socket, '0.0.0.0', 8080);
socket_listen($socket);

while (true) {
    $connection = socket_accept($socket);
    $buffer = socket_read($connection, 1024, PHP_NORMAL_READ);
    echo $buffer;
    socket_write($connection, $buffer);
    socket_read($connection, 1024);
    socket_close($connection);
}

Using this basic server will echo back what it receives, but you will need to parse HTTP requests to make a fully functional HTTP server. HTTP requests are strings that include the HTTP request method, the URI, the HTTP version, headers, and a request body. You can use the laminas-diactoros package to parse HTTP requests. The following is example code of reading the request data, parsing it, responding with a basic response:

while (true) {
    $connection = socket_accept($socket);
    $buffer = socket_read($connection, 1024, PHP_NORMAL_READ);
    $request = Serializer::fromString($buffer);
    $response = (new Response())->withStatus(200);
    $message = 'You requested ' . $request->getUriString() .
    ' with verb ' . $request->getMethod();
    $response->getBody()->write($message);
    $responseString = Serializer::toString($response);
    socket_write($client, $responseString);
    socket_close($client);
}

The server can now parse an HTTP request using the PSR-7 interface, and you can then expand the code to handle the request and provide the correct response.

This approach allows PHP applications to take direct control over the request before it is processed, but the code still needs to be expanded to handle multiple connections.

Application Event Walkthrough

By Edward Barnard

This article introduces Application Events and how they differ from Domain Events and includes a code walkthrough. The Application Event feature follows a similar structure as the Domain Event feature, and this article focuses on differences in the implementations.

   +---------------+
   | Application   |
   | Event         |
   |  - Local      |
   |  - Publish    |
   +---------------+
         |
         V
   +---------------+
   |   Domain      |
   |   Event       |
   |  - Global      |
   +---------------+

The code includes a factory to create an application service. There are two factory methods, defaultAppEvent() and dbStateChangeAppEvent(). The dbStateChangeAppEvent() method is a method to record all database changes. The test harness executes these methods, showing the flow of control of the application:

$appEvent = AppEventFactory::defaultAppEvent($action,$description);
$appEvent->save();
$appEvent->notify();

A default application event repository saves to the local table, and this method demonstrates manual database transactions with CakePHP using prepare() and execute(). This was done to allow the database layer to be interchangeable, for when the application scales to multiple databases. The save() method takes a raw MySQL string to insert, and a query to read back the data:

    public function save(string $insert, string $read, array $parms)
    {
        $connection = $this->localAppEventsTable->getConnection();
        try {
            $connection->transactional(
                function ($conn) use ($insert, $read, $parms) {
                    $statement = $conn->prepare($insert);
                    $statement->execute($parms);
                    $statement = $conn->prepare($read);
                    $statement->execute([$statement->lastInsertId()]);
                    $readback = $statement->fetchAll('assoc');
                    if (!(is_array($readback) && array_key_exists(0, $readback))) {
                        throw new DatabaseException('Event readback failed');
                    }
                    $this->readback = $readback;
                }
            );
        } catch (Exception) {
            return [];
        }
        return $this->readback;
    }

The raw MySQL strings are provided by the DefaultAppEvent class:

class DefaultAppEvent extends BaseAppEvent
{
    protected static string $insert = <<<'QUERY'
        insert into `local_app_events`
        (action, subsystem, description, detail,
        event_uuid, when_occurred, created, modified)
        values (?, ?, ?, ?, ?, now(6), now(), now())
    QUERY;

    protected static string $read =
        'select * from `local_app_events` where id = ? limit 1';
}

The BaseAppEvent provides the logic, while the DefaultAppEvent provides specific configurations. The constructor captures the necessary data to store to the database, and the manual transactions, and runs the database transaction inside a closure. This service also includes a notify() method that publishes an event to the domain event system. The save() method runs inside the database transaction, but the notify() method runs after a successful commit. The Application event is published as a Domain Event. This separation allows the use of messaging queues such as RabbitMQ for production systems:

   Application Event --> Message Queue --> Domain Event

An interface is used to define the application event and the repository to allow mixing and matching as needed. The interface for the repository includes a save() method:

interface IRAppEvent
{
    public function save(
        string $insert,
        string $read,
        array $parms
    ): array;
}

By separating constants into a PHP interface, they can be used and auto-completed by IDEs without the need of implementing methods from the same file. The repository interface simplifies testing by enabling test fixtures to implement the IRAppEvent interface.

This feature is not recommended for production as-is, but it provides the building blocks for more complex functionality. The legacy version of the application follows a similar pattern as the code above.

Converting Float Strings

By Oscar Merida

This article addresses how to correctly convert currency strings to integers representing pennies. It looks at the common pitfalls and how to approach this problem in a more robust fashion.

A common naive approach is to convert a string to a float, multiply it by 100, and then cast it to an integer:

$price = (float) "105.00";
$price = $price * 100;
var_dump($price); // float(10500)
$price = (int) $price;
var_dump($price); // int(10500)

To clean up incoming strings, you can remove unwanted characters such as $ , , , and spaces using str_replace. This gives a cleaner string ready to be converted to a float:

function priceToFloat(string $input) : float
{
    $output = str_replace(['$', ',', ' '], '', $input);
    return (float) $output;
}

It was found that the naive approach of converting to a float, multiplying, and then casting to an integer, lost precision. The float value 2487.47 was converted to integer 248746 instead of the correct value 248747. To maintain precision, use the BCMath extension with the function bcmul() to correctly convert to an integer representing pennies.

function floatToPennies(float $input) : int
{
  return (int) bcmul($input, 100);
}

The BCMath extension prevents the loss of precision during floating point math. This ensures that when converting currency strings to an integer representing pennies, you get the correct result.

    +-----------------------+
    | Currency Conversion    |
    |    Float String to    |
    |    Integer (pennies)    |
    +-----------------------+

Laracon Online 2022

By Eric Van Johnson

This article reviews Laracon Online 2022, discussing the major announcements and most interesting talks.

  • Livewire 3: Caleb Porzio discussed the complete rewrite of Livewire 3. A significant change is the inclusion of AlpineJS as part of Livewire. Livewire 3 batches network calls and uses an annotation practice.

  • Community: Caneco’s talk “The Hitchhiker’s Guide to the Laravel Community” highlighted the importance of community for new Laravel developers.

  • Database Performance: Aaron Francis presented “Database Performance for Application Developers,” covering schema design, B-tree indexes, and queries.

  • Taylor Otwell: Taylor Otwell discussed many new features released over the past year. The new yearly release cycle of Laravel means that new features are released as they are completed. New artisan commands such as the about and db:show commands were also discussed.

   +-------------+
   |  Laracon     |
   |   Online    |
   |  - Features |
   |  - Community|
   +-------------+

The entire conference is available on YouTube for free.

Beware: Tek iTek is Disruptive

By Beth Tucker Long

This article discusses the impact that conferences like php[tek] can have on a programmer’s career and life. The author recalls how her first conference, php

, provided her with a network of connections and new tools. She had originally been skeptical that she would get anything out of the conference, but it changed the way she looked at programming and the community around it. Attending conferences improves coding skills and helps build a network. Furthermore, the author became more involved in the community and gained opportunities to start speaking at conferences, which helped grow her business and work for herself full-time. Her conference experience helped her to seek therapy and a diagnosis which drastically improved her quality of life. She encourages people to attend conferences to develop skills, build networks, and learn about new tools and frameworks.

    +---------------------+
    | Conference Impact   |
    |   - Skills         |
    |   - Network        |
    |   - Growth        |
    +---------------------+

Postface

This month’s issue covered a wide range of topics relevant to the PHP ecosystem. From managing states using FSMs and improving your text editing with Vim, to building SPAs with Breeze and Inertia, enhancing cybersecurity practices, and exploring the latest PHP developments, we have covered some great and timely material. We’ve also seen how PHP can take control of HTTP connections and how to handle application events. Finally, we addressed the pitfalls of number formatting and the power of community. We hope you found this issue insightful and useful. Please join us again next month.

Index of Technology Words

  • Finite-state machines (FSMs)
  • Enumerations (enums)
  • Backed enums
  • Vim
  • Startify
  • Undo Tree
  • Vim Surround
  • Ultisnips
  • Lightline
  • Single-page applications (SPAs)
  • Laravel Breeze
  • Inertia
  • Vue.js
  • Multi-Factor Authentication (MFA)
  • Entropy
  • Phishing
  • PHP-FPM
  • FastCGI
  • Network sockets
  • PSR-7
  • PSR-15
  • Application Events
  • Domain Events
  • BCMath extension
  • Laracon Online
  • AlpineJS
  • Artisan
  • RabbitMQ
PHP Teacher magazine - October 2022
: PHP Teacher
https://phpteacher.com/blog/magazines/phpteacher-october-2022/
© 2025 PHP Teacher . All rights reserved.