Modern PHP features and ecosystem
Вашему вниманию первая профильная лекция «Modern PHP features and ecosystem» или «Make PHP great again»! Рассмотрим что нового в PHP и что будущее готовит. Но вначале дисклеймер:
- часть материала вам уже знакома и это гуд 😊;
- лекция обещает быть интересной и познавательной (за употребление слов-паразитов, жаргона и нецензурной брани простите).
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:
// 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);
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 athrow
statement in PHP 7, includingError
andException
. PHP classes cannot implement theThrowable
interface directly, and must instead extendException
.- Throwable
Exception implements Throwable
- LogicException
- BadFunctionCallException
- BadMethodCallException
- DomainException
- InvalidArgumentException
- LengthException
- OutOfRangeException
- BadFunctionCallException
- RuntimeException
- OutOfBoundsException
- OverflowException
- RangeException
- UnderflowException
- UnexpectedValueException
- LogicException
Error implements Throwable
- AssertionError
- ArithmeticError
- DivisionByZeroError
- ParseError
- TypeError
try { // code } catch (Throwable $e) { // catch all possible errors }
- Throwable
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:
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
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.
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.jsonComposer commands
composer init
,composer require vendor/package-name
,composer install
,composer dump-autoload
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 файле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();
- 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
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;
}
}
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