Modern PHP features and ecosystem

Translated into:RU
UA

Володимир Лєнч

Вова закінчив курс академії по РНР у 2017 році та з тих пір пише код в Binary Studio. Знає про CQRS та event sourcing, вміє пилити фронтенд на Vue, але розробляє додаток для моделюання баз даних на React. Любить відвідувати технічні мітапи, пити каву, та розбиратися у нових технологіях по вихідним.

Усім привіт! 👋
Вашій увазі перша профільна лекція «Modern PHP features and ecosystem» або ж «Make PHP great again»! Розглянемо що нового у PHP та що майбутнє готує. Але зпочатку дізклеймер:
  1. частина матеріалу вам вже знайома і це гуд 😊;
  2. лекція обіцяє бути цікавою та познавальною (за використання слів-паразитів, жаргону та нецензурної лайки вибачайте).
Так, до речі... з собою: термінал (командний рядок), Git, PHPStorm або Visual Studio Code. Почнемо?
Level 1 back to top

Welcome to PHP

Difficulty: Hardcore. Objectives: Get some knowledge about $php features.

PHP: Hypertext Preprocessor (or simply PHP) is a general-purpose programming language originally designed for web development. Originally PHP was just a "personal home page" created by Rasmus Lerdorf in 1995. But hey, isn't it still alive?! Hell, yes. Let's list some of the fine folks who use PHP in production, so that you understand how serious it is:

PHP (ПХП, похапэ, пэхэпэ, пиашпи, пих-пих, ПиЭйчПи) — одна з небагатьох мов програмуання, володіючи якою, можна заробити собі на хліб, $ало та воду.

  • // C/C++ language allocate memory
    int *testArray = malloc(5 * sizeof(int));
    free(testArray);
    
    // allocate memory
    $testArray = [];
    $testArray[] = 'Hi!';
    
    // memory is cleaned up by garbage collector

    Memory is managed dynamically. Written in Zend engine using C language.

  • php hello.php
  • // oop
    namespace A;
    
    interface Schedulable
    {
        public function setTime(\DateTime $datetime): void;
    }
    
    abstract class Task implements Schedulable
    {
        private $time;
    
        public function setTime(\DateTime $time): void
        {
            $this->time = $time;
        }
    }
    
    class MegaTask extends Task {}
    
    // functional
    $p = function() { return 'p'; };
    $h = fn() => 'h';
    echo $p() . $h() . $p();
    
    // procedural
    function header() {
        $title = 'Hello!';
        
        echo '<div>' . $title . '</div>';
    }
    function content() {
        echo '<div>content</div>';
    }
  • $one = '1';
    $two = 2;
    
    // int(3)
    var_dump($one + $two);
    
    $foo = null;
    $bar = false;
    
    // true
    var_dump($foo == $bar);
    
    // false
    var_dump($foo === $bar);

    But starting from PHP 7.0

    declare(strict_types=1);
    
    function tuple(int $a, int $b): array
    {
        return [$x, $y];
    }
  • Multi thread with POSIX and --pthreads extension. High-load tasks with message queue systems (RabbitMQ, etc.).

  • $kernel = new Kernel($_SERVER['APP_ENV'], $_SERVER['APP_DEBUG']);
    $request = Request::createFromGlobals();
    $response = $kernel->handle($request);
    $response->send();
    $kernel->terminate($request, $response);

    Why do we use $ in PHP?

Level 2 back to top

What's new in PHP

Difficulty: Hardcore. Objectives: Learn what PHP can offer us.

The following are some of the new features in PHP 7 and higher — it is faster (PHP 5.6 vs 8.0), memory optimized, has new exception hierarchy, introduces new syntax sugar (??, <=>, () =>), nullables (?string), scalar type declarations, , void return type, iterable type, anonymous classes. Let's take a look at some of the features in detail:

  • <script language="php">
    // cannot be used anymore
    </script>
    // no  ASP tags
    <% echo 'Oh, God!'; %>
    <?php echo 'Hello, sweety!'; ?>
    <?= "Whaat?" ?>
  • $query = 'SELECT * FROM USERS WHERE 1 = 1';
    $result = mysql_db_query($somedb, $query, $connection);
    
    // use PDO
    $pdo = new PDO('mysql:host=localhost,dbname=test');
    $query = $pdo->prepare('SELECT * FROM USERS WHERE age = :age');
    $result = $query->execute([':age' => 24]);
  • class A 
    {
        // bad, PHP 4 syntax
        public function A() 
        {
            
        }
        
        // good
        public function __construct() 
        {
             
        }
    }
  • ereg();
    eregi();
    ereg_replace();
    ...

    Use preg_* functions like preg_match();.

  • $user = ['John', 'Doe', 29];
    
    // list syntax
    list($firstname, $lastname, $age) = $user;
    
    // short array syntax
    [$firstname, $lastname, $age] = $user;
    
    // destructuring with keys
    [
        'first_name' => $firstname,
        'last_name' => $lastname,
        'age' => $age
    ] = $user;
  • $foo = function() {
        return [
            'bar' => function() { echo 'bar'; },
            'foo' => function() { echo 'foo'; }
        ];
    };
    // call a closure inside an array returned by another closure
    $foo()['bar']();
  • function getUserName() {
        return 'Pavel';
    }
    // access a character by index
    echo getUserName(){0};
  • $ball = new stdClass();
    $ball->type = 'football';
    
    $ball1 = new stdClass();
    $ball1->type = 'basketball';
    
    echo [$ball, $ball1][0]->type;
  • class Student
    {
      public static $courses = [
        'math',
        'programming',
        'databases'
      ];
    
      public static function getSchedule(): Schedule
      {
        return new Schedule();
      }
    
      public function getCredits(): Credits
      {
        return new Credits();
      }
    }
    
    class Schedule
    {
      public static $classes = 5;
    }
    
    class Credits
    {
      public static function getCreditsPerYear(): int
      {
        return 350;
      }
    }
    
    $students = [
      'Bob' => new Student(),
      'Rachel' => new Student()
    ];
    
    // access a static property on a string class name or object inside an array
    $students['Bob']::$courses;
    
    // access a static property on a string class name or object returned by a static method call on a string class name or object
    $students['Rachel']::getSchedule()::$classes;
    
    // call a static method on a string class name or object returned by an instance method call
    $students['Rachel']->getCredits()::getCreditsPerYear();
  • function foo(): callable {
      return function(): string {
        return 'Hi!';
      };
    }
    
    // call a callable returned by function
    echo foo()();
    
    $foo = function(string $param): callable {
        return function() use($param): string {
            return $param;
        };
    };
    
    // call a callable returned by another callable
    echo $foo('Hi!')();
    
    // also works with classes
    class A
    {
      public function foo(): callable
      {
        return function(): string {
          return 'Hi!';
        };
      }
    
      public static function bar(): callable
      {
        return function(): string {
          return 'Hi!';
        };
      }
    }
    
    (new A)->foo()();
    A::bar()();
  • // array key
    (expression)['key']
    
    // class property
    (expression)->prop
    
    // call a method
    (expression)->method()
    
    // static prop
    (expression)::$foo
    
    // static method
    (expression)::method()
    
    // call a callable
    (expression)()
    
    // access a string character
    (expression){0}
  • // PHP 5
    $key = isset($_GET['access_token']) ? $_GET['access_token'] : null;
    // PHP 7
    $key = $request->get('access_token') ?? null;
  • $a = 5;
    $b = 10;
    
    // $a == $b : 0 
    // $a > $b : 1
    // $a < $b : -1
    $res = $a <=> $b;
    
    $list = [-1, 10, -6, 126];
    usort($list, function(int $a, int $b) {
        return $a <=> $b;
    });
  • define('COLORS', [
      'red' => '#ff0000',
      'green' => '#00ff00',
      'blue' => '#0000ff'
    ]);
    
    echo COLORS['red'];
    
    class Drawer
    {
      // visibility added in php 7.1
      public const COLORS = [
        'red' => '#ff0000',
        'green' => '#00ff00',
        'blue' => '#0000ff'
      ];
    }
    
    echo Drawer::COLORS['red'];
  • try {
        // code
    } catch (InvalidArgumentException | MyException $e) {
        // catch only specific
    }
  • $a = intdiv(8, 3);
    $b = 8 % 3;
    
    try {
        $c = intdiv(1, 0);
    } catch (DivisionByZeroError $e) {
        // logic
    }
  • class Greeting {
        private $hello = 'Hello';
    }
    
    $closure = function(string $name) {
        return "{$this->hello}, {$name}";
    };
    
    echo $closure->call(new Greeting(), 'Bob');
  • use Some\Namespace{ClassA, ClassB};
    use function Some\Namespace{funcA, funcB};
    use const Some\Namespace{CONST_A, CONST_B};
  • interface Logger {}
    
    class BaseLogger {}
    
    trait WriteToFile {}
    
    class Util
    {
        private $logger;
    
        public function setLogger($logger)
        {
            $this->logger = $logger;
        }
    }
    
    $util = new Util();
    $util->setLogger(
        new class ($config) extends BaseLogger implements LoggerInterface {
            use WriteToFile;
    
            private $config;
    
            public function __construct(array $config)
            {
                $this->config = $config;
            }
    
            public function log($msg)
            {
                $this->write('app.log', $msg);
            }
        }
    );

    Object of anonymous class lives once at runtime. Can be used in test code to mock objects. Think before use :)

    • bool
    • float
    • int
    • string
    • array
    • iterable
    function typed(string $a, bool $b, float $c): array {}
  • declare(strict_types=1);
    
    function intSum(int $a, int $b): int
    {
        return $a + $b;
    }
    
    // good
    intSum(1, 2);
    
    // TypeError thrown
    intSum(0.1, 2);

    Use types everywhere! )

  • class Profile
    {
      private $name;
      private $age;
    
      public function __construct(string $name, ?int $age)
      {
        $this->name = $name;
        $this->age = $age;
      }
    
      public function getName(): string
      {
        return $this->name;
      }
    
      public function getAge(): ?int
      {
        return $this->age;
      }
    }
  • // php 7.1
    function doSomething(): void 
    {
      // write to log file
      return;
    }
  • function render(iterable $list)
    {
      foreach ($list as $item) {}
    }
    
    function numbers(): iterable
    {
      return [1, 2];
    }
    
    function numbersAsIterator(): iterable
    {
      $numbers = new ArrayObject([1, 2]);
      return $numbers->getIterator();
    }
  • $foo = [3, 4, 5];
    
    // [1, 2, 3, 4, 5, 6]
    $bar = [1, 2, ...$foo, 6];
  • $arr = [1, 2, 3];
    
    // [1, 4, 9]
    array_map(
      fn($n) => $n * $n,
      $arr
    );
  • Class properties now support type declarations.
    class User {
        public int $id;
        public string $name;
    }
  • 
    // before 7.4
    if (!isset($array['key'])) {
        $array['key'] = computeDefault();
    }
    
    // after
    $array['key'] ??= computeDefault();
  • Specify only required parameters, skipping optional ones. Arguments are order-independent and self-documented.
    
    function sub(int $a, int $b): int {
        return $b - $a;
    }
    
    sub(
      b: 2,
      a: 3
    );
  • Instead of PHPDoc annotations, you can now use structured metadata with PHP's native syntax.
    
    class PostsController
    {
        #[Route("/api/posts/{id}", methods: ["GET"])]
        public function get($id) { /* ... */ }
    }
    
  • Less boilerplate code to define and initialize properties.

    
    // PHP 7
    class Point {
      public float $x;
      public float $y;
    
      public function __construct(
        float $x = 0.0,
        float $y = 0.0,
      ) {
        $this->x = $x;
        $this->y = $y;
      }
    }
    
    // PHP 8
    class Point {
      public function __construct(
        public float $x = 0.0,
        public float $y = 0.0,
      ) {}
    }
  • Instead of PHPDoc annotations for a combination of types, you can use native union type declarations that are validated at runtime.
    
    class Number {
      public function __construct(
        private int|float $number
      ) {}
    }
    
    new Number('NaN'); // TypeError
    
  • The new match is similar to switch and has the following features:
    1) Match is an expression, meaning its result can be stored in a variable or returned.
    
    2) Match branches only support single-line expressions and do not need a break; statement.
    
    3) Match does strict comparisons.
    
    echo match (8.0) {
      '8.0' => "Oh no!",
      8.0 => "This is what I expected",
    };
    //> This is what I expected
    
  • Instead of null check conditions, you can now use a chain of calls with the new nullsafe operator. When the evaluation of one element in the chain fails, the execution of the entire chain aborts and the entire chain evaluates to null.
    
    $country = $session?->user?->getAddress()?->country;
    
  • PHP 8 introduces two JIT compilation engines. Tracing JIT, the most promising of the two, shows about 3 times better performance on synthetic benchmarks and 1.5–2 times improvement on some specific long-running applications.

  • 
    enum Status
    {
        case pending;
        case fullfilled;
        case canceled;
        
        public function color(): string
        {
            return match($this) 
            {
                Status::pending => 'blue',   
                Status::fullfilled => 'green',   
                Status::canceled => 'red',   
            };
        }
    }
    
  • 
    class PostData
    {
        public function __construct(
            public readonly string $title,
            public readonly string $author,
            public readonly string $text,
            public readonly DateTimeImmutable $createdAt
        ) {}
    }
    
  • 
    class StateMachine
    {
        public function __construct(
            private State $state = new DefaultState(),
        ) {
        }
    }
    
  • 
    function generateSlug(HasTitle&HasId $post): string {
        return strtolower($post->getTitle()) . $post->getId();
    }
    
  • Throwable is the base interface for any object that can be thrown via a throw statement in PHP 7, including Error and Exception. PHP classes cannot implement the Throwable interface directly, and must instead extend Exception.

    • Throwable
      • Exception implements Throwable
        • LogicException
          • BadFunctionCallException
            • BadMethodCallException
          • DomainException
          • InvalidArgumentException
          • LengthException
          • OutOfRangeException
        • RuntimeException
          • OutOfBoundsException
          • OverflowException
          • RangeException
          • UnderflowException
          • UnexpectedValueException
      • Error implements Throwable
        • AssertionError
        • ArithmeticError
        • DivisionByZeroError
        • ParseError
        • TypeError
    try {
        // code
    } catch (Throwable $e) {
        // catch all possible errors
    }
Level 3 back to top

PHP ecosystem

Difficulty: Hardcore. Objectives: Learn composer & autoloading basics, explore ecosystem.

What can I build?

Anything. PHP is mainly focused on server-side scripting, so you can do anything any other CGI program can do, such as collect form data, generate dynamic page content, or send and receive cookies. With PHP you are not limited to output HTML — PHP can do much more. These are the main areas where PHP is used:

  • Server-side apps
  • API (REST, GraphQL)
  • CRM, CMS, forum, e-commerce
  • CLI

So with PHP, you have the freedom of choosing an operating system and a web server. Furthermore, you also have the choice of using procedural programming or object oriented programming (OOP), or a mixture of them both. One of the strongest and most significant features in PHP is its support for a wide range of databases. Writing a database-enabled web page is incredibly simple using one of the database specific extensions (e.g., for mysql), or using an abstraction layer like PDO, or connect to any database supporting the Open Database Connection standard via the ODBC extension. PHP also has support for talking to other services using protocols such as LDAP, IMAP, SNMP, NNTP, POP3, HTTP, COM (on Windows) and countless others. In order to speed up the development process, write well-organized, reusable and maintainable code, you can choose to use and maybe extend a framework that is most suitable for the needs of your project:

MVC
API
CRM
  • SugarCRM
  • SuiteCRM
  • vTiger
E-commerce
Bot
  • Botman

Standards and specifications

The PHP Standard Recommendation PSR is a PHP specification published by the PHP Framework Interop Group PHP-FIG. Similar to Java Specification Request for Java, it serves the standardization of programming concepts in PHP. The aim is to enable interoperability of components and to provide a common technical basis for implementation of proven concepts for optimal programming and testing practices. The PHP-FIG is formed by several PHP frameworks founders. Each PSR is suggested by members and voted according to an established protocol to act consistently and in line with their agreed upon processes.


Package management

Composer logoComposer

Composer is a tool for dependency management in PHP. It allows you to declare the libraries your project depends on and it will manage (install/update) them for you. Composer is not a package manager in the same sense as Yum or Apt are. Yes, it deals with "packages" or libraries, but it manages them on a per-project basis, installing them in a directory (e.g. /vendor) inside your project. By default it does not install anything globally. Thus, it is a dependency manager. It does however support a "global" project for convenience via the global command. This idea is not new and Composer is strongly inspired by node's npm and ruby's bundler.

  1. composer.json
    To start using Composer in your project, all you need is a composer.json file. This file describes the dependencies of your project and may contain other metadata as well. Take a look on Laravel's composer.json

  2. Composer commands
    composer init, composer require vendor/package-name, composer install, composer dump-autoload

  3. composer.lock
    Composer uses the exact versions listed in composer.lock to ensure that the package versions are consistent for everyone working on your project. As a result you will have all dependencies requested by your composer.json file, but they may not all be at the very latest available versions (some of the dependencies listed in the composer.lock file may have released newer versions since the file was created). This is by design, it ensures that your project does not break because of unexpected changes in dependencies. Composer: Всё о .lock файле

  4. Composer autoload & PSR-4
    💩index.php before:

    require __DIR__ . '/Academy.php';
    require __DIR__ . '/PHP.php';
    
    Academy::start();
    PHP::rock();

    💡composer.json:

    "autoload": {
      "psr-4": {
        "App\\": "app/"
      },
      "classmap": [
        "directory1",
        "directory2"
      ],
      "files": [
        "MyClass.php"
      ]
    },
    "autoload-dev": {
      "psr-4": {
        "Tests\\": "tests/"
      }
    }

    🎉index.php after:

    require __DIR__ . '/vendor/autoload.php';
    
    Academy::start();
    PHP::rock();
  5. packagist.org

    When you search for a package take a look at GitHub stars, resolved issues, and last commit date.
PECL

PECL is a repository for PHP Extensions, providing a directory of all known extensions and hosting facilities for downloading and development of PHP extensions.


PHP & Web servers

  • PHP built-in local web server → php -S localhost:8888
  • apache web server → mod-php
  • nginx web server → php-fpm
Sample nginx php-fpm configuration — mywebsite.nginx.conf
server {
  listen 80;
  root /var/www/app/public;
  index index.php index.htm index.html;
  
  # ...settings
  
  # proxy files that end with ".php" through php-fpm
  location ~ \.php$ {
    fastcgi_pass unix:/var/run/php/php7.2-fpm.sock;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_buffers 16 16k;
    fastcgi_buffer_size 32k;
    include fastcgi_params;
  }
}

php-fpm_nginx

Level 4 back to top

Typical PHP development environment

Difficulty: Hardcore. Objectives: Setup your own development environment.
  • OS: Linux, Windows, MacOS
  • Version control system: Git
  • Smart IDE: PHPStorm, VSCode
  • Linters: phpstan, phpmd, phpcs
  • Debuggers: xdebug, zend debugger
  • Profilers for memory optimization: blackfire.io, xhprof
  • Standardize dev environment: vagrant-homestead, docker-laradock

Useful links


i am neo

― That's all, folks! 🐷

А на сьогодні все! Свої питання та відгуки залишайте на сайті Binary Studio Academy. Бажаю вам успіхів, не втрачайте мотивації та вчіться з задоволенням. Дякую за увагу! 🤙