Etiqueta: POO

  • Introducir objetos en un código viejo

    Introducir objetos en un código viejo

    Seguramente a vos nunca te haya pasado lo que te voy a contar, pero tenés algún amigo al que sí.

    Uno de esos pobres programadores que se enfrenta a una codebase que combina cosas como:

    function shipTo(string $street, int $number, string $postalCode, string $city): void;
    
    function getDeliveryCost(string $city, string $street, int $number, string $postalCode): float;
    
    function canPurchase(string $city, string $postalCode, int $number, string $street): bool;

    Claramente, este tipo de estructuras son una invitación al error.

    A partir de PHP 8.0 existen los argumentos nombrados, con lo cual el problema está de algún modo mitigado aunque, en mi opinión, es más un workaround que una solución como tal.

    Un mejor enfoque sería tomar una mirada algo menos pegada al código y más cercana al dominio y darse cuenta de que aquí hay un concepto implícito: la dirección postal.

    Lo correcto en esta situación sería implementar una clase que modele este concepto de dominio:

    <?php
    
    declare(strict_types=1);
    
    readonly class Address {
        public function __construct(
            private string $street,
            private int $number,
            private string $postalCode, 
            private string $city
        ) {
        }
    
        public function __toString(): string {
            return "$this->street $this->number, $this->postalCode, $this->city";
        }
    }

    Y luego hacer que las funciones usen instancias de este objeto:

    function shipTo(Address $address): void;
    
    function getDeliveryCost(Address $address): float;
    
    function canPurchase(Address $address): bool;

    Ahora sí el código de tu amigo se ve mucho más bonito, limpio, mantenible y profesional, ¿cierto?

    Pero… ¿cómo puede tu amigo implementar este cambio sin romper todo el código ya está escrito usando las firmas viejas?

    Paso intermedio: wrappers al rescate

    Aunque parezca una tarea titánica (y ciertamente lo es si se intenta hacer en un solo paso), es muy sencillo realizar el cambio en forma segura. Sólo requiere un poquito de planificación.

    La clave está en crear un intermediario que haga de adaptador entre el código viejo y el nuevo.

    Tomemos por ejemplo la función shipTo. Queremos pasar de esta firma function shipTo(string $street, int $number, string $postalCode, string $city): void a esta otra: function shipTo(Address $address): void pero, si simplemente hacemos el cambio, todas las llamadas a la vieja comenzarán a fallar.

    Lo que podemos hacer es:

    1. Crear la nueva función con la firma objetivo (function shipTo(Address $address): void)
    2. Copiar el cuerpo de la función vieja a la nueva
    3. Adaptar el código de la nueva función para que use objetos Address en lugar de parámetros individuales
    4. Cambiar el código en la función original por una llamada a la nueva implementación, pasándole como parámetro una nueva instancia de Address creada a partir de los parámetros recibidos:
    function shipTo(string $street, int $number, string $postalCode, string $city): void
    {
        shipTo(new Address($street, $number, $postalCode, $city));
    }
    1. Marcar el método (o función) viejo como #[Deprecated] (O @Deprecated si tu versión de PHP no soporta atributos), de modo que, en lo sucesivo, todo el equipo sepa que debe usar el método nuevo.

    Una vez hecho todo esto será cuestión de tiempo hasta que todo el código haya sido migrado hacia la implementación que usa objetos.

    Un detalle importante:

    Si prestaste atención abrás notado que hay dos funciones con el mismo nombre: shipTo.

    Desafortunadamente, PHP no soporta la sobrecarga de funciones como otros lenguajes (Java por ejemplo) lo cual hará que, para que todo esto funcione, sea necesario ensuciar un poquito el código usando un nombre diferente para la nueva función, por ejemplo shipToAddress.

    No es lo que más me gusta pero lo considero un precio aceptable.

    Así que ahora sí podés ir a contarle a tu amigo cómo hacer para dejar de sufrir ese código que vaya uno a saber quién escribió.

  • Ejemplo de inyección de depencias en PHP

    Ejemplo de inyección de depencias en PHP

    Un concepto muy simple y, a la vez, muy potente de la Programación Orientada a Objetos es la inyección de dependencias.

    Diría que se trata de la piedra angular de cualquier sistema desacoplado y, por lo tanto, fácil de evolucionar y testear.

    Y lo mejor de todo es que su implementación es realmente sencilla. En otras palabras: puras ventajas.

    Acompañame a darle una mirada más de cerca.

    Qué es una dependencia en POO

    Como en muchas ocasiones en este mundo de la informática, un mismo término puede referirse a diferentes cosas y, para saber realmente de qué estamos hablando es necesario poner en claro el contexto.

    Por ejemplo, si hablamos de las dependencias de una aplicación, nos estaremos refiriendo a aquellas librerías necesarias para que dicha aplicación funcione. Tema que está mejor tratado en este artículo.

    En este contexto, cuando hablo de dependencia me refiero a aquellos objetos de los que otros dependen para realizar sus tareas.

    Te pongo un ejemplo que me compartió un lector de mi newsletter y me viene muy bien para ilustrar este concepto:

    <?php
    namespace Controllers;
    
    use ApplicationService\BillFinderService;
    use ApplicationService\TotalDeductiblesByYearService;
    use Dao\DeductibleDao;
    use Infraestructure\Connection\ConnectionMySql;
    
    class FrontController extends Controller{
    
        private $connection;
    
        public function __construct()
        {
            parent::__construct();
            $this->connection = new ConnectionMySql();
        }
        
        public function home($year = null){
            if (!$year) {
                $year = new \DateTime();
                $year = $year->format('Y');
            }
            $billFinderService = new BillFinderService($this->connection);
            $totalDeductibleByYear = new TotalDeductiblesByYearService($this->connection);
            $lastBillsRegistered = $billFinderService->findByYear($year);
            $totalByDeductible = $totalDeductibleByYear->getTotalByYear($year);
            echo $this->templates->render('index', [
                'title' => 'Main menu',
                'lastBillsRegistered' => $lastBillsRegistered,
                'totalByDeductible' => $totalByDeductible,
                'year' => $year,
                'years' => [2023, 2024, 2025]
            ]);
    
        }
        
        public function error404(){
            
            echo $this->templates->render('404');
        
        }
    
        public function getBillsByDeductibleIdAndYear($deductibleId, $year){
    
            $totalDeductibleByYear = new TotalDeductiblesByYearService($this->connection);
            $bills = $totalDeductibleByYear->getBillsByDeductibleIdAndYear($deductibleId, $year);
            $deductibleDao = new DeductibleDao($this->connection);
            $deductible = $deductibleDao->findById($deductibleId);
            echo $this->templates->render('bills-by-deductible-and-year', [
                'title' => "Bills by deductible by year",
                'deductibleName' => $deductible->getName(),
                'bills' => $bills,
                'year' => $year
            ]);
        }
    }

    Aquí tenemos una clase llamada FrontController que depende, entre otras, de:

    • ConnectionMySql
    • BillFinderService
    • TotalDeductiblesByYearService
    • DeductibleDao

    ¿Por qué digo que FrontController depende de estas clases? Porque, para lograr sus objetivos se sirve de métodos de ellas, ejemplo:

    public function home($year = null){
            ...
            $lastBillsRegistered = $billFinderService->findByYear($year);
            $totalByDeductible = $totalDeductibleByYear->getTotalByYear($year);
            ...
        }

    Para que este código pueda ejecutarse, será necesario que las variables que contienen referencias a dichos objetos ($billFinderService y $totalDeductibleByYear) hayan sido previamente asignadas a instancias de sus respectivas clases.

    Algo que se está haciendo precisamente al comienzo del método:

    public function home($year = null){
    ...
            $billFinderService = new BillFinderService($this->connection);
            $totalDeductibleByYear = new TotalDeductiblesByYearService($this->connection);
    ...
    }

    Entonces… ¿cuál es el problema?

    Bueno, más que un problema es una oportunidad perdida.

    Implementando inyección de dependencias

    Empiezo por una pregunta que seguramente se pasó por alto al escribir este código:

    ¿Es necesario crear una nueva instancia de cada colaborador cada vez que se llama al método?

    Ya dije que crear la instancia es inevitable pero la pregunta apunta a determinar si el mejor momento (O lugar, como quieras verlo) para hacerlo es aquí.

    ¿Qué pasaría si modificáramos ligeramente el código para que crear las dependencias fuera responsabilidad de alguien más?

    Algo como:

    class FrontController extends Controller{
    
        private $connection;
        private $billFinderService;
        private $totalDeductiblesByYearService;
    
        public function __construct(ConnectionMySql $connection, BillFinderService $billFinderService, TotalDeductiblesByYearService $totalDeductiblesByYearService)
        {
            parent::__construct();
            $this->connection = $connection;
            $this->billFinderService = $billFinderService;
            $this->totalDeductiblesByYearService = $totalDeductiblesByYearService;
        }
        
        public function home($year = null){
            if (!$year) {
                $year = new \DateTime();
                $year = $year->format('Y');
            }
            echo $this->templates->render('index', [
                'title' => 'Main menu',
                'lastBillsRegistered' => $this->billFinderService->findByYear($year),
                'totalByDeductible' => $this->totalDeductiblesByYearService->getTotalByYear($year),
                'year' => $year,
                'years' => [2023, 2024, 2025]
            ]);
        }
    ...
    }

    Con este pequeño cambio pateamos la responsabilidad hacia arriba. Ahora es problema del cliente de la clase FrontController conseguir instancias válidas para pasarle (Ya sea creándolas él mismo o pidiéndoselas a algún otro componente).

    Esta forma de vincular una clase con sus colaboradores es lo que se conoce como inyección de dependencias.

    Ventajas de la Inyección de Dependencias

    Ahora que el tecnicismo está claro, veamos por qué vale la pena diseñar de esta manera.

    Para empezar, porque el costo de hacerlo es muy bajo. Si mirás el código inicial y lo comparás con el actual diríamos que mucha diferencia no hay, ¿cierto?

    Pero vamos a ver los aspectos más jugosos del tema:

    Performance

    Este punto depende mucho del contexto particular de tu aplicación. Si el método que crea los objetos no se ejecuta con mucha frecuencia la diferencia no va a ser mucha, pero si es al revés, te vas a estar ahorrando la creación de objetos idénticos, lo que implica menos gasto de memoria y de tiempo de procesamiento.

    Es cierto que en PHP esta ventaja se diluye por el hecho de que cada request se procesa en su propio hilo pero aún así, algo se puede ganar.

    Versatilidad

    Este punto me parece el más interesante ya que es el que da lugar a los dos siguientes.

    Pensalo así: al dejar de lado la decisión de qué instancia específica del colaborador va a usar tu clase dejás abierta la puerta a que un mismo método pueda usarse en diferentes contextos.

    Imaginá un escenario en el que existe no una si no dos instancias de la clase BillFinderService y que, dependiendo de alguna configuración se necesita usar una o la otra.

    Si el código está escrito usando Inyección de Dependencias, implementar ese cambio no supone ningún inconveniente, basta con cambiar el parámetro que se usa para construir FrontController y listo.

    Es más, dado que estás trabajando con Programación Orientada a Objetos, bien podrías pasarle una instancia de una clase derivada de BillFinderService y todo seguiría funcionando como si tal cosa.

    De hecho, este mecanismo es el que posibilita mucho de los principios SOLID.

    Testabilidad

    Un caso muy claro en el que viene bien poder cambiar la instancia de un colaborador por otra es a la hora de hacer tests unitarios.

    Si no podés aislar de sus la clase que querés testear, las cosas se te van a complicar.

    Una vez que lo viste…

    … no podés des-verlo.

    No te digo que vayas corriendo a desarmar todos tus proyectos que no usan Inyección de Dependencias pero sí te propongo que le des una segunda mirada teniendo en cuenta lo que podrías ganar si lo implementás y, de a poco, lo vayas incorporando a tu día a día.

  • ¿Puede tener éxito una aplicación en PHP estructurado?

    ¿Puede tener éxito una aplicación en PHP estructurado?

    Para hacer un proyecto grande, ¿tenés que utilizar PHP Orientado a Objetos?

    ¿Podés lograr lo mismo programando estructurado mientras que lo hagas de forma limpia y organizada?

    ¿Tendrá futuro un proyecto desarrollado con PHP estructurado?

    Estas son algunas de las preguntas que surgen cuando tenés algo de experiencia programando y empezás a pensar en grande.

    A continuación te comentaré las ventajas de incorporar estas técnicas.

    También intentaré despejar la confusión sobre ciertos mitos para que puedas tomar la mejor decisión para tu carrera.

    Pero, antes de meterme en los detalles, contestaré a la pregunta original.

    Qué determina el éxito de una aplicación

    Para responder a ¿Puede tener éxito una aplicación en PHP estructurado? lo primero que necesitas es tener una definición clara de éxito.

    Parece una obviedad, sí, pero no creas que lo es tanto.

    Si por éxito te refieres a éxito comercial la respuesta es un sí rotundo. Proyectos muy exitosos desde el punto de vista comercial como remoteok se basan en un código extremadamente sencillo.

    En otras palabras: el éxito comercial y la estructura del código no guardan una relación estrecha en general.

    Por otro lado, si por éxito te refieres a la posibilidad de generar aplicaciones a las que puedas agregar funcionalidad en forma sencilla y eficiente la Programación Orientada a Objetos te ofrece mejores chances.

    Diferencias entre Programación Estructurada y Orientada a Objetos

    Las principales características de la Programación Estructurada son:

    • Una separación muy fuerte entre datos y procesos
    • Una forma de escribir los programas indicando con mucho detalle cómo deben ser resueltos los problemas

    La POO (Programación Orientada a Objetos) no es esencialmente diferente de la estructurada.

    La forma de resolver los problemas también se basa en una serie de instrucciones que la computadora ejecutará en forma secuencial.

    Pero las similitudes llegan prácticamente hasta aquí.

    En POO los programas se parecen más a descripciones de la colaboración entre diversos actores.

    Cada actor ofrecerá a los demás algunos servicios (Llamados métodos) y, a su vez, contará con una serie de propiedades o atributos, los cuales le servirán para llevar a cabo sus tareas.

    Tomemos por ejemplo un cruce de calles con semáforo. 

    En este escenario existen:

    • 2 semáforos (Cada uno de los cuales puede tener encendida una luz color rojo, amarillo o verde)
    • Autos que se acercan hasta la bocacalle

    Lo único que hacen los semáforos es cambiar el color de la luz encendida y esperar.

    Mientras tanto, los autos pueden acelerar, frenar, abrir sus puertas, etc…

    A su vez, las luces de colores pertenecen al semáforo, junto con la acción de cambiar la luz activa.

    En un programa diseñado bajo el paradigma estructurado escribirías una función de cambiar la luz activa de un semáforo.

    Probablemente esta función recibiría el número de semáforo como parámetro y el color de la luz de cada uno se guardaría en un arreglo.

    En POO sería el propio semáforo el reponsable de cambiar su luz activa, informar al sistema cuál es su estado actual y velar por que las transiciones sean correctas (Por ejemplo, que no se pase de Rojo directo a Verde).

    Como para darle algo más de concretud a estos conceptos, y para acercarlo un poco más a algo que ya conoces, los métodos se implementan mediante funciones (Sí, con la palabra clave function) y las propiedades mediante variables (De esas que empiezan con $).

    ¿Es malo Programar en PHP Estructurado?

    Existen algunos mitos como que los programas desarrollados usando POO son más seguros o más eficientes que sus pares desarrollados usando Programación Estructurada.

    Lamentablemente, el mundo no es blanco o negro… se pueden hacer muy buenos programas estructuras y muy malos programas Orientados a Objetos.

    La calidad de los desarrollos depende de factores más sutiles que el paradigma en que estén escritos los programas.

    Lo que sí diría es que, si se escribe correctamente, el código Orientado a Objetos es más sencillo de matener y testear.

    La Programación Orientada a Objetos y el mercado laboral

    Más allá de tus preferencias personales, hay una realidad que no te conviene ignorar: la gran mayoría de las empresas de programación utilizan Programación Orientada a Objetos.

    De hecho, un número cada vez más grande de desarrollos se realizan utilizando frameworks basados en POO (Laravel, Symfony, CodeIgniter y Yii son los ejemplos más prominentes).

    Personalmente, siempre que puedo, elijo Symfony, pero los otros también tienen sus méritos.

    ¿Vale la pena aprender Programación Orientada a Objetos con PHP?

    El soporte que tiene PHP para implementar los conceptos principales de Programación Orientada a Objetos es muy bueno (especialmente en las versiones superiores a la 7.0).

    En todo caso, no tenés por qué tomar una decisión a ciegas.

    Hacé alguna prueba con una aplicación simple (Una calculadora por ejemplo). Intentá hacerla usando POO y después contame cómo te fue.


    Si se está complicando incorporar los conceptos de Programación Orientada a Objetos esta guía puede serte útil.

  • Cuándo usar una clase abstracta y cuándo una interface

    Cuándo usar una clase abstracta y cuándo una interface

    Un lector de mi libro sobre Programación Orientada a Objetos con PHP me envía esta pregunta a través de LinkedIn:

    Empecé a responderle a su mensaje pero luego se me ocurrió que sería mejor aprovechar y contestarlo en público así que aquí voy.

    Empecemos por comprender de qué se trata cada uno.

    Qué es una clase abstracta

    En su definición más cruda una clase se dice abstracta si no es posible utilizarla para crear objetos (instancias).

    Suena un poco raro, ¿no? ¿Para qué quiero tener una clase si no es para crear instancias?

    La explicación viene asociada al concepto de Herencia (Tema para otro artículo en todo caso).

    Una clase abstracta puede usarse como base de una jerarquía.

    Se define de esta forma:

    <?php
    
    abstract class Abstracta
    {
        public function metodoConcreto()
        {
            return true;
        }
    
        abstract public function metodoAbstracto();
    }

    Notá cómo el método metodoConcreto() tiene definición (está el cuerpo completo) y como en cambio, de metodoAbstracto() sólo está su declaración (Aparte de llevar la palabra «abstract» como modificador).

    Para usarla en una clase concreta se necesita que la hija complete las definiciones que su padre (o alguno de sus ancestros) han dejado inconclusas:

    class Concreta extends Abstracta
    {
        public function metodoAbstracto()
        {
            return true;
        }
    }

    En la vida real, este tipo de estructura viene muy bien para implementar, por ejemplo, hooks.

    La idea será algo como esto: la clase padre (abstracta) define una serie de operaciones bastante complejas y repetitivas y deja una o dos funciones sin definir para que la clase hija escriba aquí sus particularidades.

    Un ejemplo que me viene a la mente es un ORM basado en ActiveRecord.

    En este caso, habría una clase Record que tendría un método save y podría tener algún método tipo preSave/postSave para que cada tipo de registro en particular pueda intercalar validaciones u operaciones encadenadas.

    Qué es una interface

    Una interface puede definirse como una declaración de métodos abstractos.

    En este sentido se parece a una clase abstracta… la diferencia (a simple vista al menos) es que una interface no puede definir métodos (Sólo puede declararlos).

    Se ve de esta forma:

    interface UnaInterface
    {
        public function f1();
        
        public function f2();
    }

    Según los teóricos más puristas de la Programación Orientada a Objetos toda clase debería implementar al menos una interface.

    Por lo general se utilizan interfaces cuando se quiere unificar nombres de métodos pero seguir manteniendo comportamientos que no tienen nada que ver uno con el otro.

    De hecho, las interfaces suelen utilizarse como factor común entre clases que no pertenencen a una misma jerarquía.

    Por ejemplo, si tomamos una clase Book y otra clase Invoice sería difícil establecer una relación jerárquica entre ellas (Ni Book es un tipo especial de Invoice ni viceversa).

    Sin embargo, es muy probable que ambas clases se beneficien de contar con un método print, aunque la forma específica de responder a esa llamada (Es decir, la forma de imprimir) será muy diferente para cada uno de ellos.

    Habiendo clases abstractas… ¿por qué se necesitan interfaces?

    La respuesta a esta pregunta tiene que ver con algunos problemas de implementación.

    En algunos lenguajes (C++ por ejemplo) existe lo que se conoce como Herencia Múltiple (La posibilidad de que una clase tenga más de un antecesor directo).

    Es decir, el esquema se vería algo como:

    Se ve que la clase TA hereda de Student Y de Faculty. Hasta ahí no hay problema… pero ¿qué pasa si Student define un método con el mismo nombre que Faculty?

    Cuando se invoque $ta->metodo() ¿cuál debería ejecutarse? (asumiendo por supuesto que TA no tiene una definición propia de ese método).

    Una forma elegante de resolver este problema es impedir la herencia múltiple (Eso es lo que hacen, entre otros, Java y PHP).

    Pero la necesidad de reutilizar nombres (y comportamientos) a lo largo de diferentes jerarquías de clases no desaparece… de ahí surge la idea de las interfaces (y los traits… tema para otro artículo).

    Un punto importante: una clase (en PHP al menos) puede implementar tantas interfaces como se desee (pero sólo puede extender una clase base).

    Cómo decidir si conviene una clase abstracta o una interface

    Pues bien, ahora sí estamos listos para responder a la pregunta original 🙂

    Debe usarse una clase abstracta cuando se está modelando una jerarquía de clases y una interface cuando se pretende homogeneizar nombres entre objetos que no están emparentados. Compartir en X

    Esto parece una obviedad, pero no es tan así… a veces nos encontramos con objetos que parecen estar relacionados mediante herencia pero en realidad no es así.

    Para determinar si este es el caso vale preguntarse: «¿Es este objeto un caso particular de sus predecesores?» (Un auto es un vehículo).

    El uso de interfaces nos permite «olvidar» momentáneamente con qué tipo de objetos estoy trabajando.

    En esto se basa el principio de segregación de la interface (La I de SOLID)

    Un ejemplo que me viene a la mente es algo que utilizamos hace unos años para una red social de viajes en la que trabajaba.

    Originalmente esta red social permitía a sus usuarios subir sus fotos y diarios de viaje (compartir sus experiencias en formato similar a un blog).

    Una foto y un diario de viaje tenían bastante pocas similitudes (De hecho las clases que las representaban no tenían ningún ancestro en común).

    Un día surgió la necesidad de dotar al sistema de la posibilidad de que otros usuarios dejaran sus opiniones sobre las fotos y/o los diarios de los demás.

    Entonces se nos ocurrió agregarle a la clase usuario un método opinar().

    Pero no podíamos crear un método opinarSobreFoto( Foto $foto ) y otro opinarSobreDiario( Diario $diario )… en rigor de verdad podríamos haberlo hecho pero no era para nada mantenible.

    Una forma mejor de resolverlo fue crear una interface Opinable que tanto la clase Foto como la clase Diario implementaran y luego el método Usuario::opinar pudiera usar, dando lugar a algo como:

    <?php
    
    class Usuario
    {
       public function opinar( Opinable $opinable, string $texto )
       {
           return new Opinion( $opinable, $this, $texto ); 
       }
    } 

    De esta forma, agregar otro opinable (Por ejemplo, un destino visitado) no supone ningún problema para la clase Usuario.

    Y así todos felices 🙂

  • Qué son los métodos mágicos de PHP

    Qué son los métodos mágicos de PHP

    Una de las características que más me gusta de PHP son los métodos «mágicos».

    En realidad, como en la vida real, no se trata de magia como tal, si no de buenos trucos.

    De lo que se trata en este caso es de métodos que son invocados automáticamente (ante ciertos sucesos o eventos).

    La principal característica que tienen estos métodos es que sus nombres comienzan con __ (doble underscore).

    Si venís programando con objetos en PHP (al menos desde la versión 5), reconocerás estos (Especialmente el primero):

    __construct

    __destruct

    Puede que no lo supieras, pero el constructor y destructor de la clase pertenecen a un grupo más grande de métodos (conocidos como «mágicos»).

    Veamos algunos de ellos:

    __toString

    Este es otro de los más conocidos.

    Su función es retornar una cadena (string) que represente al objeto.

    Por ejemplo, si tuviéramos una clase Persona:

    <?php
    
    class Persona
    {
         private $nombre;
         private $apellido;
    
         public function __construct( $nombre, $apellido )
         {
                  $this->nombre = $nombre;
                  $this->apellido = $apellido;
          }
    }

    Podría resultar interesante que, al momento de imprimir por pantalla los datos de la persona se viera su nombre y su apellido.

    En este escenario podríamos definir un método __toString de la siguiente forma:

    public function __toString()
    {
        return $this->nombre.' '.$this->apellido;
    }

    Y luego hacer algo como:

    $persona = new Persona( "Mauro", "Chojrin" );
    
    echo $persona;

    Cuando el intérprete lea echo $persona automáticamente buscará en la definición de la clase Persona un método __toString y, si lo encuentra, lo invocará.

    Otros métodos mágicos interesantes

    Como decía, existen varios métodos mágicos que vale la pena conocer:

    __call

    El método __call es invocado en forma automática cuando se realiza una llamada a un método no definido explícitamente.

    En nuestro ejemplo anterior, sería el caso de invocar algo como $persona->intercambiar();.

    Lo primero que hará el intérprete será buscar un método específico llamado «intercambiar», al no encontrarlo buscará una definción de un método __call. En caso de encontrarla lo invocará pasando como parámetros el nombre del método buscado («intercambiar» en este caso y la lista de argumentos).

    La definición del método __call será algo como:

    public function __call( $metodo, array $argumentos )
    {
      ...
    }

    Este método es muy útil cuando se quiere armar familias de métodos similares.

    El ejemplo más claro que me viene a la mente es el que usaba Doctrine en su versión 1.

    Tenía un método findBy() que recibía un array de criterios de búsqueda.

    Muchas veces era muy cómodo poder escribir algo como findByName() (Donde «Name» se correspondía con el nombre de un campo de la tabla en cuestión).

    Es claro que Doctrine no puede adivinar todos los nombres de campos que a uno se le pueden ocurrir (y generar un método findByX()para cada uno…).

    Ahí entonces una solución posible (basada en __call) es algo como:

    public function __call( $method, array $arguments )
    {
       $find = "findBy";
    
       if ( substr( $method, 0, strlen( $find ) ) == $find ) {
           $field = substr( $method, strlen( $find ) );
        
           return $this->findBy( [ $field => $arguments[0] ] );
       }
    }

    De esta forma es posible llamar a cualquier método findByX como si estuviese definido.

    __set y __get

    Los métodos __set y __get son invocados cuando se intenta asignar (u obtener) una variable no pública de una clase.

    El contar con estos métodos permite, por ejemplo, ahorrar algo de código (No se requiere escribir un setter/getter para cada propiedad):

            public function __set( string $var, $val )
            {
                    $this->$var = $val;
            }
    
            public function __get( string $var )
            {
                    return $this->$var;
            }
    

    Y luego:

    $persona = new Persona( "Mauro", "Chojrin" );
    echo $persona->nombre.PHP_EOL;
    $persona->apellido = "Perez";
    echo $persona.PHP_EOL;

    Claro que, en este caso, bien podríamos haber dejado las variables como públicas y era más fácil, ¿cierto? :p

    Pero el tener estos métodos me permite también, por ejemplo, crear propiedades virtuales:

            public function __set( string $var, $val )
            {
                    if ( $var === "nombreCompleto" ) {
                            $parts = preg_split('/ /', $val);
                            $this->nombre = $parts[0];
                            $this->apellido = $parts[1];
                    } else {
                            $this->$var = $val;
                    }
            }
    
            public function __get( string $var )
            {
                    if ( $var === 'nombreCompleto' ) {
    
                            return $this->__toString();
                    }
                    return $this->$var;
            }

    Y al final:

    $persona = new Persona( "Mauro", "Chojrin" );
    $persona->nombreCompleto= "Miguel Perez";
    echo $persona->nombre.','.$persona->apellido.PHP_EOL;

    Más métodos mágicos

    Hay unos cuántos métodos mágicos más (algunos más útiles que otros, cierto) que podés consultar en la documentación oficial de PHP.

    Esta es una característica bastante avanzada que a veces se conoce también como meta-programación.