Infocurci
* Claudio Curci  /  Php developer
'
* Claudio Curci  /  Php developer

2. lezione: l’evoluzione della OOP in PHP 8


Nella lezione precedente abbiamo visto come PHP moderno abbia introdotto un sistema di tipizzazione molto più rigoroso e affidabile. In questa lezione faremo un passo ulteriore: capiremo quali sono le novità, tecniche ma anche concettuali, che possiamo utilizzare per costruire software robusto ed estendibile.

Dalle classi di servizio al dominio

Finora abbiamo visto con una certa frequenza classi come questa:

<?php
class CalciatoreManager
                {
                    public function saveCalciatore() {}
                    public function deleteCalciatore() {}
                    public function getCalciatore() {}
                }
più che un vero paradigma Object-Oriented, si tratta quasi di un ‘super helper’ o di una sorta di namespace per raccogliere funzioni affini. Questo approccio rende il codice poco estendibile, perché tende ad agglomerare funzioni che “fanno qualcosa su un oggetto” ma che non rappresentano l’oggetto stesso, ovvero il suo dominio. L’approccio corretto è una classe come questa
<?php
class Calciatore
                {
                    private string $email;
                    private bool $active;

                    public function activate(): void
                    {
                        $this->active = true;
                    }

                    public function changeEmail(string $email): void
                    {
                        $this->email = $email;
                    }
                }
Abbiamo una classe che rappresenta realmente l’utente e ne protegge lo stato con variabili private. Qui viene fuori il vero concetto di ‘dominio’.

Possiamo dire addio a classi come questa

<?php
class UserManager
                {
                    public function saveUser() {}
                    public function sendEmail() {}
                    public function generatePdf() {}
                    public function calculateDiscount() {}
                }
Meglio usare classi diverse, in cui ognuna ha una responsabilità precisa:
  • UserRepository
  • MailService
  • PdfGenerator
  • DiscountCalculator

Meno ereditarietà, più composition

PHP, da sempre, non ha volutamente offerto la possibilità di utilizzare l’ereditarietà multipla, scelta applicata a suo tempo anche nel mondo Java ma non in C++.
L’ereditarietà multipla può portare diversi problemi. Ipotizziamo per un attimo che PHP la supporti:

<?php
class MailSender
                {
                    public function sendMail() {}
                }

                class Logger
                {
                    public function log() {}
                }

                class UserService extends MailSender, Logger
                {
                }
fin qui sembrerebbe tutto innocuo, ma cosa accadrebbe se sia MailSender che Logger avessero due metodi omonimi (il classico “save()”)? E cosa accadrebbe se a forza di ereditare classi, venisse fuori un albero complesso con tanto di eredità tra “cugini”?
Con un approccio sempre più tipizzato, PHP 8 spalanca le porte a soluzioni come questa
<?php
class MailSender
                {
                    public function sendMail(): void
                    {
                        echo "Mail sent";
                    }
                }
                class Logger
                {
                    public function log(string $message): void
                    {
                        echo $message;
                    }
                }
                class UserService
                {
                    public function __construct(
                        private MailSender $mailer,
                        private Logger $logger
                    ) {
                    }

                    public function register(): void
                    {
                        $this->logger->log("User registration");

                        $this->mailer->sendMail();
                    }
                }
nessuna eredità ma un utilizzo diretto di ciò che ci occorre. Anziché il concetto di “IS-A”, entra in gioco “HAS-A”. L’ereditarietà va bene quando c’è un’effettiva relazione, ad esempio in un sistema con diversi tipi di utenze va benissimo creare un User base e poi altre sottoclassi (AdminUser, ManagerUser, SystemUser) che lo ereditano.
Non va intesa come un semplice modo per riutilizzare codice, ma piuttosto come una soluzione per gestire reali relazioni.

Dependency Injection: oggetti tipizzati

Un classico esempio di come vengono passati oggetti all'interno di una classe è questo

<?php
class UserService
                    {
                        private Logger $logger;
        
                        public function __construct()
                        {
                            $this->logger = new Logger();
                        }
                    }
funziona ma rende molto forte la dipendenza tra i due oggetti, renendone difficile un'eventuale sostituzione in futuro, nonché più complessi i test. Con il paradigma Dependency Injection possiamo fare così
<?php
class UserService
                  {
                  public function __construct(
                        private LoggerInterface $logger
                    ) {
                  }
                }
richiamando UserService in questo modo
<?php
$service = new UserService(
                     new FileLogger()
                );
in questo modo il codice è meno vincolato e più flessibile. La Dependency Injection è alla base di ambienti come Symfony, Laravel, Magento2.

Che differenza c’è tra un’interfaccia ed una classe astratta?

Chiaramente questa non è una differenza introdotta da PHP 8, ma viene da se che, alla luce di quanto abbiamo detto finora, l’utilizzo di classi astratte ed interfacce diventa sempre più rilevante. Un’interfaccia si limita a determinare “lo scheletro” di una classe, dichiarandone tutti i metodi ma lasciando l’implementazione degli stessi alle classi che la utilizzeranno. In pratica rappresentano il contratto che definisce il comportamento delle classi. Anche una classe astratta dichiara i metodi, ma può definirne il codice. Classico esempio, quello dei corrieri in un sistema di e-commerce dove torna comodo definire il metodo getName() (che verosimilmente per quasi tutti ritornerà “return $this-> name”) dichiarando abstract e lasciando vuoti i vari setWeight(), setTax(), getPrice() ecc. ecc. La comodità delle interfacce/classi astratte appare evidente quando le passiamo come parametri, sfruttando così il cosiddetto "polimorfismo" class OrderService { public function __construct( private LoggerInterface $logger ) { } } In questo caso OrderService potrà essere istanziata sia con un logger di File che con uno di Database, senza alcuna modifica.

Cosa sono i trait?

I trait rappresentano una sorta di “snippet” che aggiunge le proprie funzioni alle classi in cui viene usato:

<?php
trait Timestampable
                {
                    private DateTime $createdAt;

                    public function date2Mysql(): string
                    {
                        $dt =  new \DateTime();
                        return $dt->format("Y-m-d H:i:s");
                    }
                }

                class Allenatore
                {
                    use Timestampable;
                }
                
                class Calciatore
                {
                    use Timestampable;
                }
                
                $allenatore = new Allenatore();
                $calciatore = new Calciatore();
                
                echo 'Allenatore inserito in data '.$allenatore->date2Mysql();
                echo "\n";
                echo 'Calciatore inserito in data '.$calciatore->date2Mysql();
Va comunque detto che i trait vanno usati con parsimonia, non tanto perchè rappresentano un problema in sé, quanto perchè un utilizzo frequente potrebbe indicare un anti-pattern in corso.

Alcune modifiche sintattiche aggiunte in PHP 8

Proprietà e classi readonly

<?php
class User
                {
                    public function __construct(
                        public readonly string $email
                    ) {
                    }
                }
E' stata aggiunta la proprietà 'readonly', utile in modo da evitare che una variabile evidentemente immutabile venga cambiata in corso d'opera.
A partire da PHP 8.2 è possibile usare il readonly direttamente nella dichiarazione della classe:
<?php
readonly class User
                {
                    public function __construct(
                        public string $email,
                        public string $name
                    ) {
                    }
                }
in questo modo tutte le proprietà saranno readonly.
Attenzione: “readonly” non significa che la variabile è in sola lettura in senso assoluto (sarebbe inutile!); diventa tale dopo che è stata valorizzata una prima volta, nel costruttore o in un metodo successivo.

constructor property promotion

<?php
class User
                {
                    public function __construct(
                        private string $name
                    ) {
                    }
                }
sostituisce con codice piu snello
<?php
class User
            {
                private string $name;

                public function __construct(string $name)
                {
                    $this->name = $name;
                }
            }

Dal 1997, il Php a Roma!

Claudio Curci "La vera efficienza si trova nella serenità." (Henry David Thoreau)

"Guardati dalla sterilità di una vita sempre indaffarata." (Socrate)

Da quasi trent'anni mi dedico alla programmazione, con oltre 20 anni di esperienza come freelance. Certificato booking.com. Mi occupo di tutto quello che gravita attorno al Php: software custom, Magento, Woocommerce, Prestashop, Wordpress, Joomla!, Moodle.
Infocurci Questo sito rispetta gli utenti:
  • Non ospita nessuna pubblicità
  • Non utilizza cookie e non profila nulla
  • Non spreca risorse energetiche per asettiche illustrazioni AI
Navigate serenamente, siete i benvenuti.

Ambienti / piattaforme di sviluppo

Amazon Booking.com CodeIgniter Joomla Magento Moodle Kelkoo Kigo PayPal Symfony Wordpress Airbnb FormaLms Laravel Prestashop Shopify Whatsapp