NSWI153: Advanced Web Applications

PHP : Composer, Dependency Injection, PHPUnit, GitLab

Preliminaries

Please read this section at least one day prior to the seminar. This section outlines what you are expected to know, be able to do, or prepare in advance. Following these instructions will help you get the most out of the seminar and ensure smooth participation.

Preliminaries : Knowledge, Skills, and Competence


Before the start of the practical, you should be able to:

  • Create and run PHP script locally.
  • Create a PHP class and instantiate it.
  • Explain the idea behind the PHP autoloading.

Preliminaries : Using your own computer


This slide applies to you only if you plan to use your own computer for the practical.

Before the start of the practical, make sure that:

  • You have PHP installed.
  • You can run PHP from a command line using following command:
    
              php --version
            
  • You have downloaded Composer. It is enough to download just the composer.phar file.
  • You can execute following command:
    
              php composer.phar
            

Objectives

  • Composer & Autoloading
  • Dependency Injection
  • PHPUnit
  • Code quality
  • Continuous Integration (CI)

Demonstration: Composer & Autoloading

Objective of this demonstration is to create a minimal project using Composer. Next you will create a class and utilize Autoloading to work with the class.

Discussion


Briefly discuss following questions.

  • What is autoloading?
  • What are the benefits of autoloading for PHP?
  • How can you implement autoloading yourself?
  • How psr-4 autoloading works?

Project with Composer 1 / 2


Use following command to initialize a project. Create an empty project with psr-4 autoloading. Do not add any dependencies!


      php composer.phar init
    

Continue to the next slide once you are done >>>

Project with Composer 2 / 2


Create {vendor-name}\{project-name}\User\UserManager class in ./src/User/UserManager.php. Use the following code for a class definition.


      class UserManager {

          public function getUsers()
          {
              return [['name' => 'Ailish', 'email' => 'ailish@email.com']];
          }

      }
    

Create an instance of the class in ./index.php file and var_dump result of getUsers method call. Do not manually require or include the UserManager.php file. Make sure it works by running following command.


      php ./index.php
    

Continue to the next slide once you are done >>>

Debriefing


Explore compose.json, try to change the namespace mapping. It wont work, as the mapping is stored in the vendor directory. You need to run following command to regenerate the vendor directory.


      // Install dependencies
      php composer.phar install

      // Update dependencies
      php composer.phar update

      // Update autoloading
      php composer.phar dump-autoload
    

Congratulations, if you have followed the instructions, you have just reached the end of this exercise.

Demonstration: PHPUnit

You already know PHPUnit from the winter semestr. Now, let us use it in a better way!

PHPUnit : Getting Started


Continue with the previous code! Add PHPUnit into your project. Follow the "Getting started" tutorial and adopt it for our code.

Make sure user can execute following command and that there are two passing tests.


      ./vendor/bin/phpunit --testdox tests
    

Discuss what version of PHPUnit are we using and why.

Exercise: Dependency injection

Autoloading is only part of the picture. It allows us to get class definition when we need it. Yet, what if we do not want to write "new"?

Dependency injection


Briefly discuss following questions.

  • What is a dependency injection?
  • What are the benefits and drawbacks?

Assignment: Dependency injection 1 / 2


Continue with the previous code! Install PHP implementation of dependency injection (PHP-DI) using following command.


      php composer.phar require php-di/php-di
    

Create {vendor-name}\{project-name}\Service\Mailer class with following content.


      class Mailer {

          public function mail($recipient, $content)
          {
              print("$recipient : $content\n");
          }

      }
    

Continue to the next slide once you are done >>>

Assignment: Dependency injection 2 / 2


Inject the Mailer into the UserManager with a constructor. For more details on "how to do it" see Getting started with PHP-DI guide. Add the following method to the UserManager


      public function notifyUsers(string $content)
      {
          foreach ($this->getUsers() as $user) {
              $this->mailer->mail($user['email'], $content);
          }
      }
      

In the ./index.php file:

  1. Create an instance of UserManager using the PHP-DI.
  2. Call notifyUsers('Hello world!') on the instance.

Run the ./index.php script and check the result, fix any errors.

Continue to the next slide once you are done >>>

Debriefing

...

Congratulations, if you have followed the instructions, you have just reached the end of this exercise.

Demonstration: Code quality and PHP

PHP_CodeSniffer is an essential development tool that ensures your code remains clean and consistent.

PHP_CodeSniffer


Continue with the previous code! The objective is to employ phpcs to detect code style violations. First we need to obtain phpcs. Next we can manually run it using following command.


      ./vendor/bin/phpcs --standard=PSR12 ./src/
    

PSR-12: Extended Coding Style requires compliance with PSR-1: Basic Coding Standard. PSR-1 defines how to write PHP code, including how to write PHP tags, encoding, namespaces, naming, etc.. PSR-12 adds requirements for coding standard, tags, files, indentations, keywords, control structures, etc..

Some of the issues can be fixed using phpcbf.


      ./vendor/bin/phpcbf --standard=PSR12 ./src/
    

Make sure PHP_CodeSniffer does not produce any error on any of your PHP files!

Exercise: Continuous Integration (CI)

Continuous Integration is a software development practice where each member of a team merges their changes into a codebase together with their colleagues changes at least daily. Each of these integrations is verified by an automated build (including test) to detect integration errors as quickly as possible.

GitHub Actions


For example, GitHub utilizes GitHub Actions. You would put your actions definitions into ".github/workflows" directory as YAML files.


      name: couchdb
      on: push
      jobs:
        publish-docker:
          runs-on: ubuntu-24.04
          steps:
            - name: Checkout
              uses: actions/checkout@v3
            - name: Login to Docker Registry
              uses: docker/login-action@v2
              with:
                registry: ghcr.io
                username: ${{ github.actor }}
                password: ${{ secrets.GITHUB_TOKEN }}
            - name: Set up Docker Buildx
              uses: docker/setup-buildx-action@v2
            - name: Build and Push
              uses: docker/build-push-action@v3
              with:
                push: true
                tags: ghcr.io/organization/docker-image-name:${{ github.ref_name }}
                cache-from: type=gha
                cache-to: type=gha,mode=max
    

GitLab integration


For gitlab we use .gitlab-ci.yml in the repository root.

Assignment: Run PHPUnit on push

GitLab: ./practical-01/


Continue with the previous code! Create ".gitlab-ci.yml" so that on push the PHPUnit tests are executed.

Hint: look for PHPUnit in the examples in the GitLab documentation.

Upload all code into given GitLab repository. The repository is in the "teaching/nswi153/{academic-year}" group. Use path as indicated at top of this slide.

Check the (Pipeline) execution on your GitLab repository page.

Try to be reasonably efficient. Reference solution runs in about 20 seconds.

Questions, ideas, or any other feedback?

Please feel free to use the anonymous feedback form.