* 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:

                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
                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

                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:

                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
                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

                    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ì
                 class UserService
                  {
                  public function __construct(
                        private LoggerInterface $logger
                    ) {
                  }
                }
            
richiamando UserService in questo modo
                 $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:

                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

                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:
                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

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

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

Dal 1997, il Php a Roma!

Claudio Curci
Da quasi trent'anni mi dedico alla programmazione, con oltre 20 anni di esperienza come freelance. Credo in un approccio al lavoro in cui la competenza si sposa con la serenità, dove le urgenze sono vizi, non virtù. Mi impegno a supportare i clienti con risposte chiare e soluzioni concrete, evitando inutili tecnicismi. "La vera efficienza si trova nella serenità." – Henry David Thoreau
Infocurci Questo sito non utilizza cookie, non mostra nessuna pubblicità e non profila nulla. 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