Posted by Greg Doak & Rainer Kraft on Feb 16, 2023

PHP UK Conference

We were lucky enough to be attendees at the recent PHPUK Conference hosted at the Brewery in Central London. The conference ran from Wednesday 15th to Thursday 16th of February 2023 and over the course of these two days we got to experience a variety of knowledgeable developers speaking on topics they were passionate about.

The format of the conference is that there are three talks going on at the same time, and these talks are split into different lanes which gives people the opportunity to pick and choose which talks they want to see.

Below we’re highlighted our three favourite talks.

What’s New in PHP 8.1 and 8.2

This talk by Derick Rethans covered the new features available in PHP 8.1 and 8.2.

Enumerations were released as part of PHP 8.1 which is a special kind of object in PHP that’s it own class but also all possible values are single-instances of that class. Enumeration are useful for fixed data structures with a fixed number of possible values, consider the following enum that deals with currency:

<?php
enum Currency {
    case GBP;
    case EUR;
}

var_dump(Currency::GBP);

The dump will return enum(Currency::GBP) which is useful for type verification, however this isn’t too useful for handling values. This is where Backed Enumerations can be used, at present you can only use the string or integer primitive types:

<?php
enum Currency: string {
    case GBP = '£';
    case EUR = '€';
}

var_dump(Currency::GBP->value);

The dump will return the value string(2) "£". As we have a list of known values we can also use tryFrom to match a value against a referenced property:

<?php
enum Currency: string {
    case GBP = '£';
    case EUR = '€';
}

var_dump(Currency::tryFrom('$'));
var_dump(Currency::tryFrom('£'));

When a value is matched it returns it’s referenced enumeration class of enum(Currency::GBP) and when not matched it defaults to NULL value.

We can also take advantage of from:

<?php
enum Currency: string {
    case GBP = '£';
    case EUR = '€';
}

var_dump(Currency::from('$'));

This will throw a ValueError exception if the value is not within the enum value set Uncaught ValueError: "$" is not a valid backing value for enum "Currency"

Continuous Integration: Tips & Tricks by Paul Dragoonis

This talk delved into the complexities of continuous integration and what can be done to reduce your deployment times. The first trick was to parallelise actions where possible, for example instead of running your unit and integration tests after each other these can be split to run in parallel to reduce the total time for these actions to run.

The second trick is something we’ll look at implementing in our own projects which is splitting scripts between running within the docker container and running on the host system in order to make our CI processes more repeatable. For example consider the below Github actions configuration for running unit and integration tests:

.github/workflows/test-backend-application.yml
name: Test Application

on:
  workflow_dispatch:
  push:
    branches:
      - master

jobs:
  test-backend-app:
    name: Test Backend Application
    runs-on: ubuntu-latest
    steps:
      - name: Run Unit Tests
        run: docker run \
          -rm \
          -v $(pwd):/app \
          ${BACKEND_APPLICATION_PHP_IMAGE} \
          bash -c 'bin/phpunit --group unit'
      - name: Run Integration Tests
        run: docker run \
          -rm \
          -v $(pwd):/app \
          ${BACKEND_APPLICATION_PHP_CONTAINER} \
          bash -c 'bin/phpunit --group integration'

As the execution of the tests is directly linked to the Github actions implementation in order to run these tests locally we would have to copy and paste commands. In order to simplify this we can split this command into smaller pieces in shell files and a utilise Docker Compose.

docker-compose.ci.yml
version: "3.4"
services:
  unit:
    image: ${BACKEND_APPLICATION_PHP_IMAGE}
    entrypoint: ./ci-cd/steps/run-unit-tests.sh
    volumes:
      - .:/app
  integration:
    image: ${BACKEND_APPLICATION_PHP_IMAGE}
    entrypoint: ./ci-cd/steps/run-integration-tests.sh
    volumes:
      - .:/app
ci-cd/steps/run-unit-tests.sh
#!/usr/bin/env bash
set -eux

docker-compose -f docker-compose.ci.yml run -it unit
ci-cd/scripts/run-unit-tests.sh
#!/usr/bin/env bash
set -eux

bin/phpunit --group unit

Directory Layout

|-- Project
    |-- .github
    |   |-- workflows
    |       |-- test-backend-application.yml
    |-- ci-cd
    |   |-- scripts
    |   |   |-- run-unit-tests.sh
    |   |   `-- run-integration-tests.sh
    |   `-- steps
    |       |-- run-unit-tests.sh
    |       `-- run-integration-tests.sh
    |-- src
    |-- tests
    `-- docker-compose.ci.yml            
.github/workflows/test-backend-application.yml
name: Test Application

on:
  workflow_dispatch:
  push:
    branches:
      - master

jobs:
  test-backend-app:
    name: Test Backend Application
    runs-on: ubuntu-latest
    steps:
      - name: Run Unit Tests
        run: ci-cd/steps/run-unit-tests.sh
      - name: Run Integration Tests
        run: ci-cd/steps/run-integration-tests.sh

Now in order to run these tests we only need to run the shell file that is relevant, if we are on our host we can run ./ci-cd/steps/run-unit-tests.sh, alternatively if we are within the container we can run ./ci-cd/scripts/run-unit-tests.sh. This provides a consistent manner in which to run our scripts, confident that different environments run exactly the same.

Extending the PHP Language with Static Analysis

This talk covered the use of using static analysis tools to enforce custom business rules.

We can take a look of how to use static analysis tools to enforce business rules, in the below example consider the following setup to send text messages:

Use Case

Here we have the code, calling the sendMessage() method of the TextMessageGateway. The gateway places the message in a queue and the TextMessageQueueProcessor is consuming these messages and calling the sendMessage() method of the TextMessageSender.

Our business rule states that all text messages our code sends must be through the TextMessageQueueProcessor and not TextMessageSender directly. While there are visibility modifiers in PHP with public, protected, and private we need more fine grain control for our use case. We could also try to document this rule and be vigilant during code reviews but these are not completely safe.

Here we can use the static analysis tool PHPStan to add our constraint that only our TextMessageQueueProcessor can call TextMessageSender directly.

<?php declare(strict_types=1);

namespace App\PhpstanRules;

use App\PhpstanRuleDemo\TextMessageQueueProcessor;
use App\PhpstanRuleDemo\TextMessageSender;
use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;

class TextMessageSenderCallCheckRule implements Rule
{
    public function getNodeType(): string
    {
        return MethodCall::class;
    }

    public function processNode(Node $node, Scope $scope): array
    {
        $callingClass = $scope->getClassReflection()->getName();

        if ($callingClass === TextMessageQueueProcessor::class) {
            return [];
        }

        $type = $scope->getType($node->var);

        foreach ($type->getReferencedClasses() as $targetClass) {
            if ($targetClass === TextMessageSender::class) {
                return [
                    RuleErrorBuilder::message(
                        sprintf(
                            "Can not call %s from %s",
                            $targetClass,
                            $callingClass
                        )
                    )->build()
                ];
            }
        }

        return [];
    }
}

Now we tag our newly created run in phpstan.neon so PHPStan can run it.

parameters:
    level: max
    paths:
      - src

services:
-
    class: App\PhpstanRules\TextMessageSenderCallCheckRule
    tags:
      - phpstan.rules.rule

Now when we run PHPStan we will be able to detect if a class within our code base is calling TextMessageSender and alert us to this problem.

In Conclusion

While we’ve only covered our favourite three out of the possible twenty-eight talks that ran this year we would recommend this conference to other developers with an interest in PHP. The talks are designed to cover everything from beginner topics right up to advanced concepts within the PHP ecosystem.

This conference has been running for seventeen years, and it’s always run to a high standard, the talks are easy to find and the keynote talks in the morning convey a lot of the information we need to make the decision of which talks to attend.

Keep an eye out for news regarding PHP UK 2024!

Jobs at MyBuilder and Instapro

We need experienced software engineers who love their craft and want to share their hard-earned knowledge.

View vacancies
comments powered by Disqus