# data[].attributes.title
Rediscovering PHP: Modern Practices Beyond Legacy

# data[].attributes.blurb.html
PHP powers over half of the web, yet many developers still remember it as spaghetti-code of the ’90s. In this session, you’ll see how PHP has evolved into a modern platform featuring JIT compilation, strong typing, attributes, and a thriving ecosystem of packages & frameworks. We’ll dispel common myths – no, PHP does know objects – and explore how today’s best practices bring PHP in line with, and often ahead of, other mainstream languages.

With concise examples and case studies, you’ll learn how modern PHP leverages tools like Composer, PSR standards, static analysis, and Dependency Injection to build maintainable, scalable applications. Whether you’re coming from Java, JavaScript, Go or Rust, you’ll discover how PHP’s strengths – its simplicity, performance gains in PHP 8+, and vibrant community – can complement your existing toolset.

What You’ll Take Away:

PHP 8+ Essentials: JIT, union types, attributes, constructor property promotion

Ecosystem Mastery: Composer, Packagist, PSR standards, static analysis (PHPStan/Psalm)

Framework Showcases: Symfony and Laravel feature highlights and architecture patterns

Interoperability & Tooling: Integrating with Docker, REST/gRPC APIs, and CI/CD pipelines

Community & Culture: How to engage with PHP events, open-source projects, and cross-pollinate ideas

Join us to bridge the gap between the PHP community and the wider development world—ignite your curiosity and see why PHP deserves a spot in every polyglot’s toolbox.

# data[].attributes.slidedeck.data[].slides[].desc.html
Rediscovering PHP Modern Practices Beyond Legacy Horacio Gonzalez 2026-04-20

# data[].attributes.slidedeck.data[].slides[].desc.html
Who are we? Introducing myself and introducing Clever Cloud

# data[].attributes.slidedeck.data[].slides[].desc.html
Horacio Gonzalez @LostInBrittany Spaniard Lost in Brittany Old(ish) Developer

# data[].attributes.slidedeck.data[].slides[].desc.html
Clever Cloud From Code to Product

# data[].attributes.slidedeck.data[].slides[].desc.html
You’re going to talk about PHP ?!?! The elephpant IS in the room

# data[].attributes.slidedeck.data[].slides[].desc.html
So yeah, I know both PHP and its reputation… And in 2004 is was quite accurate, PHP felt like the Wild West

# data[].attributes.slidedeck.data[].slides[].desc.html
If you think PHP is inconsistent, insecure, and strictly procedural… You were right 15 years ago !

# data[].attributes.slidedeck.data[].slides[].desc.html
I’ve been a Java developer since ‘97 And I have done tons of frontend in HTML/CSS/JS

# data[].attributes.slidedeck.data[].slides[].desc.html
But I’ve also written and debugged lots of PHP I even made a living of it!

# data[].attributes.slidedeck.data[].slides[].desc.html
For years I kept away from PHP I reckon I was a bit snobbish about it…

# data[].attributes.slidedeck.data[].slides[].desc.html
Until I joined Clever Cloud and rediscovered it And, mate, it was so much better!

# data[].attributes.slidedeck.data[].slides[].desc.html
PHP is not like PHP 4 anymore PHP powers 75%+ of the web… and 90% of that usage looks nothing like WordPress 3.0

# data[].attributes.slidedeck.data[].slides[].desc.html
PHP – The Language Syntax for the Java Mind

# data[].attributes.slidedeck.data[].slides[].desc.html
Code Organization: Namespaces & Autoloading The Good Old Days The Modern Way include ‘config.php’; namespace App\Http\Controllers; include ‘database.php’; use App\Services\UserService; include ‘functions.php’; use App\Models\User; include ‘models/user.php’; use App\Mail\WelcomeEmail; include ‘models/admin.php’; include ‘controllers/user_controller.php’; class UserController include ‘lib/email.php’; { include ‘lib/validator.php’; public function __construct( // … 50 more includes …// Everything is global private UserService $userService $user = new User(); ) {} $admin = new Admin(); // Name collision disasters waiting to happen: function send_email() { } // From email.php … } function send_email() { } // Oops, redeclared in another file! PSR-4 Autoloading: clean namespace hierarchy

# data[].attributes.slidedeck.data[].slides[].desc.html
Typing The Good Old Days The Modern Way function createUser($name, $age, $active) { declare(strict_types=1); // <—- The “Java Switch” // Is $age an int? “20”? 20.5? // Contract-based programming. If you violate the contract, // Is $active a boolean? 0? 1? “yes”? // the application stops immediately. No guessing. final class UserFactory if ($active == 1) { { return “User: ” . $name . ” is ” . $age; // Typed Arguments, Return Type, Visibility } public function create( string $name, int $age, bool $isActive // Returns null implicitly if condition fails ): string { // Inconsistent return types! if ($isActive) { } return sprintf(“User: %s is %d”, $name, $age); } $u = createUser(“Horacio”, “45”, “true”); // Works, but relies on “Magic” casting. throw new InactiveUserException(); } } From “Anything Goes” to Strictness

# data[].attributes.slidedeck.data[].slides[].desc.html
The Type System Flex (Union & Intersection Types) The Modern Way public function handle(User|Guest $entity): Response { // Type system knows it’s one of these // IDE autocomplete works // Static analysis validates it return match(true) { $entity instanceof User => $this->handleUser($entity), $entity instanceof Guest => $this->handleGuest($entity), }; } // Return type unions: much cleaner than Optional public function findUser(int $id): User|null { return $this->repository->find($id); } // Must satisfy BOTH interfaces public function log(Loggable&Serializable $event): void No need for overloading boilerplate.

# data[].attributes.slidedeck.data[].slides[].desc.html
Constructor Property Promotion (PHP 8.0+) The Good Old Days The Modern Way class User class UserProfile { { public function __construct( private string $name; // Visibility + Type + Name = Property Created & Assigned private string $email; private string $name, private int $age; private string $email, private int $age, public function __construct(string $name, string $email, int $age) ) {} { // That’s it. Properties are declared, assigned, and accessible. $this->name = $name; // Getters are optional - you decide based on your encapsulation needs. $this->email = $email; $this->age = $age; } public function getName(): string { return $this->name; } public function getEmail(): string { return $this->email; } public function getAge(): int { return $this->age; } } // Want to make it immutable like a Java Record? // Just add ‘readonly’ (PHP 8.2+): class ImmutableUser { public function __construct( } public readonly string $name, public readonly string $email, ) {} }

# data[].attributes.slidedeck.data[].slides[].desc.html
Attributes: the End of “Code in Comments” The Good Old Days The Modern Way class UserController use Symfony\Component\Routing\Attribute\Route; { use Symfony\Component\Security\Http\Attribute\IsGranted; /** * @Route(“/api/users”, methods={“GET”}) class UserController

@IsGranted(“ROLE_ADMIN”) {

// Native syntax. “Route” is a real class found via “use”

This is technically a comment. #[Route(‘/api/users’, methods: [‘GET’])]

If I typo “@Rout”, nothing crashes… #[IsGranted(‘ROLE_ADMIN’)]

until the app runs and the route is missing. public function list(): Response */ { public function list() // … { } // … } } } // How to make your own Attribute? It’s just a class! #[Attribute] class MyCustomMetadata { public function __construct(public string $info) {} }

# data[].attributes.slidedeck.data[].slides[].desc.html
Enums (The Real Deal) The Good Old Days The Modern Way class BlogPost / 1. Defined using ‘enum’ keyword { enum Status: string // These are just strings. { // Nothing prevents me from passing “garbage” to a function expecting a case Draft = ‘draft’; status. case Published = ‘published’; const STATUS_DRAFT = ‘draft’; case Archived = ‘archived’; const STATUS_PUBLISHED = ‘published’; // 2. THEY CAN HAVE METHODS! (Just like Java) const STATUS_ARCHIVED = ‘archived’; public function color(): string { public function setStatus(string $status) { return match($this) { // I have to manually validate if $status is one of the allowed self::Draft => ‘gray’, constants self::Published => ‘green’, if (!in_array($status, [self::STATUS_DRAFT, …])) { self::Archived => ‘red’, throw new Exception(); }; } $this->status = $status; } } } } // 3. Type Safety is absolute function updateStatus(Status $newStatus): void { // Impossible to pass “foo” or “draft” string here. } // 4. Accessing values echo Status::Published->value; // “published” echo Status::Published->color(); // “green”

# data[].attributes.slidedeck.data[].slides[].desc.html
Match Expressions The Good Old Days // The problem: The Modern Way $status = 200; // 1. Loose comparison (‘200’ string matches 200 int) // 2. Requires ‘break’ (easy to forget = fallthrough bugs) // 1. Assign result directly to variable // 3. Verbose assignment logic // 2. Strict comparison (‘200’ string will NOT match 200 int) // 3. Comma-separated values for multiple matches $status = 200; $message = null; $message = match ($status) { 200 => ‘OK’, switch ($status) { 300, 301 => ‘Redirect’, case 200: 404 => ‘Not Found’, $message = ‘OK’; break; default => ‘Unknown’, }; case 300: case 301: // Bonus: Pattern matching in PHP 8 allows logic inside match! $message = ‘Redirect’; /* break; $result = match (true) { case 404: $age >= 18 => ‘Adult’, $message = ‘Not Found’; break; default: $message = ‘Unknown’; } $age < 18 }; */ => ‘Minor’,

# data[].attributes.slidedeck.data[].slides[].desc.html
PHP – The Ecosystem Professionalism & Tooling

# data[].attributes.slidedeck.data[].slides[].desc.html
The PSR Standards (PHP-FIG) PHP-FIG: Agree on Interfaces, not Implementations PSRs (PHP Standard Recommendations).

# data[].attributes.slidedeck.data[].slides[].desc.html
PSR: Dependency Inversion The Good Old Days // Tightly coupled to “Monolog” // Decoupled. Relies on PSR-3 Standard. use Monolog\Logger; use Psr\Log\LoggerInterface; class UserManager class UserManager { { public function __construct( public function __construct( private Logger $logger // <—- HARD DEPENDENCY private LoggerInterface $logger // <—- INTERFACE ONLY ) {} ) {} public function create() { public function create() { // Tied to Monolog’s specific method names // Guaranteed to exist on ANY PSR-3 compliant logger $this->logger->addInfo(‘User created’); $this->logger->info(‘User created’); } } } } True Dependency Inversion at a community scale

# data[].attributes.slidedeck.data[].slides[].desc.html
Dependency Management: Composer Java - Maven pom.xml <dependency> PHP - Composer composer.json { <groupId>org.slf4j</groupId> “require”: { <artifactId>slf4j-api</artifactId> “monolog/monolog”: “^3.0” <version>2.0.7</version> } </dependency> }
<!— And then refresh your IDE… —>
// Terminal command: composer require monolog/monolog Dependency Management: Solved since 2012

# data[].attributes.slidedeck.data[].slides[].desc.html
PHPStan: The Compiler Substitute $ phpstan PHP class UserManager $ vendor/bin/phpstan analyse src —level=9 { /**

@param array<User> $users <— PHPStan reads this “Generic” definition 1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% */ public function activateAll(array $users): void { ——— ————————————————————————————————Line foreach ($users as $user) { // Runtime: If $user is NOT a User object, this crashes HERE. $user->activate(); } } } // The Bug: $managers = [new User(), new User(), “oops, a string”]; (new UserManager())->activateAll($managers); src/UserManager.php ——— ————————————————————————————————18 Parameter #1 $users of method UserManager::activateAll() expects array<User>, array{User, User, string} given. Type string is not subtype of User. ——— ————————————————————————————————[ERROR] Found 1 error

# data[].attributes.slidedeck.data[].slides[].desc.html
PHP – Performance & Architecture

# data[].attributes.slidedeck.data[].slides[].desc.html
JIT & Performance Sources: Kinsta, Tideways, ICDSoft, PHP manual, various public benchmarks

# data[].attributes.slidedeck.data[].slides[].desc.html
The Frameworks: Symfony & Laravel Symfony is Spring Boot. Laravel is Rails. We aren’t scripting anymore; we are architecting

# data[].attributes.slidedeck.data[].slides[].desc.html
Symfony: The Enterprise Standard The Good Old DaysJava - Spring @Service PHP - Symfony use Symfony\Component\DependencyInjection\Attribute\Autowire; public class ReportGenerator { private final Mailer mailer; class ReportGenerator { @Autowired // Constructor Injection is the standard. public ReportGenerator(Mailer mailer) { // The container automatically injects the implementation o this.mailer = mailer; public function __construct( } private MailerInterface $mailer } ) {} } If you know Spring, you know Symfony ■ Dependency Injection Container ■ Decoupling ■ Stability

# data[].attributes.slidedeck.data[].slides[].desc.html
Laravel: The “Developer Happiness” Framework If you love the speed of Express or Rails, Laravel is that, but typed. It’s not just a framework; it’s a platform. ● ● ● ● ● Eloquent ORM: (ActiveRecord) Incredibly expressive Queue Workers: (Laravel Horizon) Redis-backed queues out of the box. Real-time: (Laravel Reverb) WebSockets without Node.js. Serverless: (Laravel Vapor) AWS Lambda deployment helper. … PHP - Laravel <?php // Find all active users and email them… in 3 lines. User::query() ->where(‘active’, true) ->get() ->each(fn(User $user) => Mail::to($user)->send(new WelcomeEmail()));

# data[].attributes.slidedeck.data[].slides[].desc.html
New Runtimes: Async & Long-Running Processes PHP used to die after every request. Not anymore.

# data[].attributes.slidedeck.data[].slides[].desc.html
The Game Changer: FrankenPHP A modern application server written in Go, built on top of Caddy, that embeds the PHP interpreter and most popular extensions ● ● ● ● Worker Mode Early Hints (HTTP 103) Real-Time Mercure hub Static Binary

# data[].attributes.slidedeck.data[].slides[].desc.html
PHP – Myths, Misconceptions, and Reality Checks

# data[].attributes.slidedeck.data[].slides[].desc.html
“PHP is slow” Myth busted!

# data[].attributes.slidedeck.data[].slides[].desc.html
“PHP has no types” Myth busted!

# data[].attributes.slidedeck.data[].slides[].desc.html
“PHP apps don’t scale” Myth busted!

# data[].attributes.slidedeck.data[].slides[].desc.html
“PHP code is spaghetti” Modern PHP uses Strict MVC or Clean Architecture If you write 1000 lines of code in a single file in Java, it’s spaghetti. If you do it in PHP, it’s spaghetti. Bad code is language-agnostic Myth busted!

# data[].attributes.slidedeck.data[].slides[].desc.html
“PHP is insecure” Myth busted!

# data[].attributes.slidedeck.data[].slides[].desc.html
“PHP developers are isolated” Myth busted!

# data[].attributes.slidedeck.data[].slides[].desc.html
WordPress Question: “Why Does It Still Look Old? Chose backward compatibility Chose modernization Legacy Deployment ≠ Language Limitation

# data[].attributes.slidedeck.data[].slides[].desc.html
PHP – The Polyglot Conclusion

# data[].attributes.slidedeck.data[].slides[].desc.html
Reality Check: When PHP Isn’t the Answer Honest Assessment: Choose the Right Tool

# data[].attributes.slidedeck.data[].slides[].desc.html
PHP isn’t the language you remember It’s a pragmatic, high-performance tool that respects your time. Don’t hate it, add it to your toolbox

# data[].attributes.slidedeck.data[].slides[].desc.html
PHP – Live Demo / Code Walkthrough

# data[].attributes.slidedeck.data[].slides[].desc.html
Meetup driven development A challenge proposed by Zenika Clermont-Ferrand

# data[].attributes.slidedeck.data[].slides[].desc.html
Show me the code! https://github.com/LostInBrittany/clash-of-pop-culture

# data[].attributes.slidedeck.data[].slides[].desc.html
Clash of Pop Culture https://clash-of-pop-culture.cleverapps.io/index.html

# data[].attributes.slidedeck.data[].slides[].desc.html
That’s all, folks! Thank you all!