Buscar..


Introducción

Las clases y los objetos se utilizan para hacer que su código sea más eficiente y menos repetitivo al agrupar tareas similares.

Una clase se usa para definir las acciones y la estructura de datos utilizada para construir objetos. Los objetos se construyen utilizando esta estructura predefinida.

Sintaxis

  • class <ClassName> [ extends <ParentClassName> ] [ implements <Interface1> [, <Interface2>, ... ] { } // Declaración de clase
  • interface <InterfaceName> [ extends <ParentInterface1> [, <ParentInterface2>, ...] ] { } // Declaración de interfaz
  • use <Trait1> [, <Trait2>, ...] ; // Usar rasgos
  • [ public | protected | private ] [ static ] $<varName>; // Declaración de atributo
  • const <CONST_NAME>; // Declaración constante
  • [ public | protected | private ] [ static ] function <methodName>([args...]) { } // Declaración de método

Observaciones

Clases y componentes de interfaz

Las clases pueden tener propiedades, constantes y métodos.

  • Las propiedades mantienen variables en el alcance del objeto. Pueden inicializarse en la declaración, pero solo si contienen un valor primitivo.
  • Las constantes deben inicializarse en la declaración y solo pueden contener un valor primitivo. Los valores constantes se fijan en el momento de la compilación y no pueden asignarse en el tiempo de ejecución.
  • Los métodos deben tener un cuerpo, incluso uno vacío, a menos que el método se declare abstracto.
class Foo {
    private $foo = 'foo'; // OK
    private $baz = array(); // OK
    private $bar = new Bar(); // Error!
}

Las interfaces no pueden tener propiedades, pero pueden tener constantes y métodos.

  • Las constantes de interfaz deben inicializarse en la declaración y solo pueden contener un valor primitivo. Los valores constantes se fijan en el momento de la compilación y no pueden asignarse en el tiempo de ejecución.
  • Los métodos de interfaz no tienen cuerpo.
interface FooBar {
    const FOO_VALUE = 'bla';
    public function doAnything();
}

Interfaces

Introducción

Las interfaces son definiciones de las clases de API públicas que deben implementarse para satisfacer la interfaz. Funcionan como "contratos", especificando lo que hace un conjunto de subclases, pero no cómo lo hacen.

La definición de interfaz es muy parecida a la definición de clase, cambiando la class palabra clave a interface :

interface Foo {

}

Las interfaces pueden contener métodos y / o constantes, pero no atributos. Las constantes de interfaz tienen las mismas restricciones que las constantes de clase. Los métodos de interfaz son implícitamente abstractos:

interface Foo {
    const BAR = 'BAR';

    public function doSomething($param1, $param2);
}

Nota: las interfaces no deben declarar constructores o destructores, ya que estos son detalles de implementación en el nivel de clase.

Realización

Cualquier clase que necesite implementar una interfaz debe hacerlo usando la palabra clave implements . Para hacerlo, la clase debe proporcionar una implementación para cada método declarado en la interfaz, respetando la misma firma.

Una sola clase puede implementar más de una interfaz a la vez.

interface Foo {
    public function doSomething($param1, $param2);
}

interface Bar {
    public function doAnotherThing($param1);
}


class Baz implements Foo, Bar {
    public function doSomething($param1, $param2) {
        // ...
    }

    public function doAnotherThing($param1) {
        // ...
    }
}

Cuando las clases abstractas implementan interfaces, no necesitan implementar todos los métodos. Cualquier método no implementado en la clase base debe ser implementado por la clase concreta que lo extiende:

abstract class AbstractBaz implements Foo, Bar {
    // Partial implementation of the required interface...
    public function doSomething($param1, $param2) {
        // ...
    }
}

class Baz extends AbstractBaz {
    public function doAnotherThing($param1) {
        // ...
    }
}

Observe que la realización de la interfaz es una característica heredada. Al extender una clase que implementa una interfaz, no es necesario volver a declararla en la clase concreta, porque está implícita.

Nota: Antes de PHP 5.3.9, una clase no podía implementar dos interfaces que especificaban un método con el mismo nombre, ya que causaría ambigüedad. Las versiones más recientes de PHP lo permiten siempre que los métodos duplicados tengan la misma firma [1] .

Herencia

Al igual que las clases, es posible establecer una relación de herencia entre interfaces, utilizando la misma palabra clave se extends . La principal diferencia es que se permite la herencia múltiple para las interfaces:

interface Foo {

}

interface Bar {

}

interface Baz extends Foo, Bar {

}

Ejemplos

En el siguiente ejemplo tenemos una interfaz de ejemplo simple para un vehículo. Los vehículos pueden ir hacia adelante y hacia atrás.

interface VehicleInterface {
    public function forward();

    public function reverse();

    ...
}

class Bike implements VehicleInterface {
    public function forward() {
        $this->pedal();
    }

    public function reverse() {
        $this->backwardSteps();
    }

    protected function pedal() {
        ...
    }

    protected function backwardSteps() {
        ...
    }

    ...
}

class Car implements VehicleInterface {
    protected $gear = 'N';

    public function forward() {
        $this->setGear(1);
        $this->pushPedal();
    }

    public function reverse() {
        $this->setGear('R');
        $this->pushPedal();
    }

    protected function setGear($gear) {
        $this->gear = $gear;
    }

    protected function pushPedal() {
        ...
    }

    ...
}

Luego creamos dos clases que implementan la interfaz: Bicicleta y Coche. Bicicletas y automóviles internamente son muy diferentes, pero ambos son vehículos, y deben implementar los mismos métodos públicos que proporciona VehicleInterface.

La tipografía permite que los métodos y funciones soliciten interfaces. Supongamos que tenemos una clase de estacionamiento, que contiene vehículos de todo tipo.

class ParkingGarage {
    protected $vehicles = [];

    public function addVehicle(VehicleInterface $vehicle) {
        $this->vehicles[] = $vehicle;
    }
}

Debido a que addVehicle requiere un $vehicle del tipo VehicleInterface no una implementación concreta, podemos ingresar Bicicletas y Autos, que el ParkingGarage puede manipular y usar.

Constantes de clase

Las constantes de clase proporcionan un mecanismo para mantener valores fijos en un programa. Es decir, proporcionan una forma de asignar un nombre (y una comprobación de tiempo de compilación asociada) a un valor como 3.14 o "Apple" . Las constantes de clase solo se pueden definir con la palabra clave const : la función de definir no se puede usar en este contexto.

Como ejemplo, puede ser conveniente tener una representación abreviada del valor de π en todo el programa. Una clase con valores const proporciona una forma sencilla de mantener dichos valores.

class MathValues {
    const PI = M_PI;
    const PHI = 1.61803;
}

$area = MathValues::PI * $radius * $radius;

Se puede acceder a las constantes de clase utilizando el operador de dos puntos dobles (denominado operador de resolución de alcance) en una clase, al igual que las variables estáticas. Sin embargo, a diferencia de las variables estáticas, las constantes de clase tienen sus valores fijos en el momento de la compilación y no se pueden reasignar a (por ejemplo, MathValues::PI = 7 produciría un error fatal).

Las constantes de clase también son útiles para definir cosas internas de una clase que pueden necesitar cambiar más adelante (pero no cambian con la frecuencia suficiente para justificar el almacenamiento en, por ejemplo, una base de datos). Podemos hacer referencia a esto internamente utilizando el resolutor de alcance self (que funciona tanto en implementaciones estáticas como en instancias)

class Labor {
    /** How long, in hours, does it take to build the item? */
    const LABOR_UNITS = 0.26;
    /** How much are we paying employees per hour? */
    const LABOR_COST = 12.75;

    public function getLaborCost($number_units) {
         return (self::LABOR_UNITS * self::LABOR_COST) * $number_units;
    }
}

Las constantes de clase solo pueden contener valores escalares en versiones <5.6

A partir de PHP 5.6 podemos usar expresiones con constantes, lo que significa que las declaraciones matemáticas y las cadenas con concatenación son constantes aceptables

class Labor {
    /** How much are we paying employees per hour? Hourly wages * hours taken to make */
    const LABOR_COSTS = 12.75 * 0.26;

    public function getLaborCost($number_units) {
         return self::LABOR_COSTS * $number_units;
    }
}

A partir de PHP 7.0, las constantes declaradas con define ahora pueden contener matrices.

define("BAZ", array('baz'));

Las constantes de clase son útiles para algo más que almacenar conceptos matemáticos. Por ejemplo, si prepara una tarta, puede ser conveniente tener una clase de Pie capaz de tomar diferentes tipos de fruta.

class Pie {
    protected $fruit;

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

Entonces podemos usar la clase Pie como tal

$pie = new Pie("strawberry");

El problema que surge aquí es que, al crear una instancia de la clase Pie , no se proporciona una guía sobre los valores aceptables. Por ejemplo, al hacer una tarta "boysenberry", podría escribirse incorrectamente "boisenberry". Además, podríamos no apoyar una tarta de ciruela. En su lugar, sería útil tener una lista de tipos de frutas aceptables ya definidas en algún lugar en el que tendría sentido buscarlas. Di una clase llamada Fruit :

class Fruit {
    const APPLE = "apple";
    const STRAWBERRY = "strawberry";
    const BOYSENBERRY = "boysenberry";
}

$pie = new Pie(Fruit::STRAWBERRY);

Enumerar los valores aceptables como constantes de clase proporciona una sugerencia valiosa sobre los valores aceptables que acepta un método. También asegura que las faltas de ortografía no puedan superar el compilador. Si bien la new Pie('aple') y la new Pie('apple') son aceptables para el compilador, la new Pie(Fruit::APLE) producirá un error de compilación.

Finalmente, el uso de constantes de clase significa que el valor real de la constante puede modificarse en un solo lugar, y cualquier código que use la constante tiene automáticamente los efectos de la modificación.

Si bien el método más común para acceder a una constante de clase es MyClass::CONSTANT_NAME , también se puede acceder a él mediante:

echo MyClass::CONSTANT;

$classname = "MyClass";
echo $classname::CONSTANT; // As of PHP 5.3.0

Las constantes de clase en PHP se denominan convencionalmente todas en mayúsculas con guiones bajos como separadores de palabras, aunque cualquier nombre de etiqueta válido se puede usar como un nombre de constante de clase.

A partir de PHP 7.1, las constantes de clase ahora se pueden definir con visibilidades diferentes del alcance público predeterminado. Esto significa que tanto las constantes protegidas como las privadas pueden definirse ahora para evitar que las constantes de clase se filtren innecesariamente en el ámbito público (consulte Método y visibilidad de la propiedad ). Por ejemplo:

class Something {
    const PUBLIC_CONST_A = 1;
    public const PUBLIC_CONST_B = 2;
    protected const PROTECTED_CONST = 3;
    private const PRIVATE_CONST = 4;
}

definir constantes de clase vs

Aunque esta es una construcción válida:

function bar() { return 2; };

define('BAR', bar());

Si intentas hacer lo mismo con las constantes de clase, obtendrás un error:

function bar() { return 2; };

class Foo {
    const BAR = bar(); // Error: Constant expression contains invalid operations
}

Pero puedes hacer:

function bar() { return 2; };

define('BAR', bar());

class Foo {
    const BAR = BAR; // OK
}

Para más información, ver constantes en el manual .

Usando :: class para recuperar el nombre de la clase

PHP 5.5 introdujo la ::class sintaxis de ::class para recuperar el nombre completo de la clase, teniendo en cuenta el alcance del espacio de nombres y las declaraciones de use .

namespace foo;
use bar\Bar;
echo json_encode(Bar::class); // "bar\\Bar"
echo json_encode(Foo::class); // "foo\\Foo"
echo json_encode(\Foo::class); // "Foo"

Lo anterior funciona incluso si las clases ni siquiera están definidas (es decir, este fragmento de código funciona solo).

Esta sintaxis es útil para funciones que requieren un nombre de clase. Por ejemplo, se puede usar con class_exists para verificar que una clase existe. No se generarán errores, independientemente del valor de retorno en este fragmento:

class_exists(ThisClass\Will\NeverBe\Loaded::class, false);

Enlace estático tardío

En PHP 5.3 y versiones posteriores, puede utilizar el enlace estático tardío para controlar desde qué clase de clase de propiedad o método se llama. Se agregó para superar el problema inherente con el resolutor self:: scope. Toma el siguiente código

class Horse {
    public static function whatToSay() {
         echo 'Neigh!';
    }

    public static function speak() {
         self::whatToSay();
    }
}

class MrEd extends Horse {
    public static function whatToSay() {
         echo 'Hello Wilbur!';
    }
}

Usted esperaría que la clase MrEd anule la función principal whatToSay() . Pero cuando corremos esto obtenemos algo inesperado.

Horse::speak(); // Neigh!
MrEd::speak(); // Neigh!

El problema es que self::whatToSay(); solo puede referirse a la clase Horse , lo que significa que no obedece a MrEd . Si cambiamos al resolutor static:: scope, no tenemos este problema. Este nuevo método le dice a la clase que obedezca la instancia que lo llama. Así obtenemos la herencia que estamos esperando.

class Horse {
    public static function whatToSay() {
         echo 'Neigh!';
    }

    public static function speak() {
         static::whatToSay(); // Late Static Binding
    }
}

Horse::speak(); // Neigh!
MrEd::speak(); // Hello Wilbur!

Clases abstractas

Una clase abstracta es una clase que no puede ser instanciada. Las clases abstractas pueden definir métodos abstractos, que son métodos sin cuerpo, solo una definición:

abstract class MyAbstractClass {
    abstract public function doSomething($a, $b);
}

Las clases abstractas deben extenderse por una clase secundaria que luego puede proporcionar la implementación de estos métodos abstractos.

El propósito principal de una clase como esta es proporcionar un tipo de plantilla que permita a las clases de niños heredar, "forzando" una estructura a adherirse. Vamos a elaborar sobre esto con un ejemplo:

En este ejemplo estaremos implementando una interfaz Worker . Primero definimos la interfaz:

interface Worker {
    public function run();
}

Para facilitar el desarrollo de futuras implementaciones de Worker, crearemos una clase de trabajo abstracta que ya proporciona el método run() desde la interfaz, pero especifica algunos métodos abstractos que deben ser completados por cualquier clase secundaria:

abstract class AbstractWorker implements Worker {
    protected $pdo;
    protected $logger;

    public function __construct(PDO $pdo, Logger $logger) {
        $this->pdo = $pdo;
        $this->logger = $logger;
    }

    public function run() {
        try {
            $this->setMemoryLimit($this->getMemoryLimit());
            $this->logger->log("Preparing main");
            $this->prepareMain();
            $this->logger->log("Executing main");
            $this->main();
        } catch (Throwable $e) {
            // Catch and rethrow all errors so they can be logged by the worker
            $this->logger->log("Worker failed with exception: {$e->getMessage()}");
            throw $e;
        }
    }

    private function setMemoryLimit($memoryLimit) {
        ini_set('memory_limit', $memoryLimit);
        $this->logger->log("Set memory limit to $memoryLimit");
    }

    abstract protected function getMemoryLimit();

    abstract protected function prepareMain();

    abstract protected function main();
}

En primer lugar, hemos proporcionado un método abstracto getMemoryLimit() . Cualquier clase que se extienda desde AbstractWorker debe proporcionar este método y devolver su límite de memoria. El AbstractWorker luego establece el límite de memoria y lo registra.

En segundo lugar, AbstractWorker llama a los prepareMain() y main() , después de registrar que se han llamado.

Finalmente, todas estas llamadas de método se han agrupado en un bloque try - catch . Entonces, si alguno de los métodos abstractos definidos por la clase secundaria produce una excepción, capturaremos esa excepción, la registraremos y la volveremos a realizar. Esto evita que todas las clases secundarias tengan que implementar esto ellos mismos.

Ahora definamos una clase secundaria que se extiende desde AbstractWorker :

class TranscactionProcessorWorker extends AbstractWorker {
    private $transactions;

    protected function getMemoryLimit() {
        return "512M";
    }

    protected function prepareMain() {
        $stmt = $this->pdo->query("SELECT * FROM transactions WHERE processed = 0 LIMIT 500");
        $stmt->execute();
        $this->transactions = $stmt->fetchAll();
    }

    protected function main() {
        foreach ($this->transactions as $transaction) {
            // Could throw some PDO or MYSQL exception, but that is handled by the AbstractWorker
            $stmt = $this->pdo->query("UPDATE transactions SET processed = 1 WHERE id = {$transaction['id']} LIMIT 1");
            $stmt->execute();
        }
    }
}

Como puede ver, TransactionProcessorWorker fue bastante fácil de implementar, ya que solo teníamos que especificar el límite de memoria y preocuparnos por las acciones reales que debía realizar. No se necesita ningún manejo de errores en el TransactionProcessorWorker porque eso se maneja en el AbsractWorker .

Nota IMPORTANTE

Cuando se hereda de una clase abstracta, todos los métodos marcados como abstractos en la declaración de la clase del padre deben ser definidos por el hijo (o el propio niño también debe estar marcado como abstracto); Además, estos métodos deben definirse con la misma visibilidad (o una menos restringida). Por ejemplo, si el método abstracto se define como protegido, la implementación de la función debe definirse como protegida o pública, pero no privada.

Tomado de la documentación de PHP para la abstracción de clase .

Si no define los métodos de clases abstractas primarias dentro de la clase secundaria, se le lanzará un Error Fatal de PHP como el siguiente.

Error grave: la Clase X contiene 1 método abstracto y, por lo tanto, debe declararse abstracto o implementar los métodos restantes (X :: x) en

Separación de nombres y carga automática

Técnicamente, la carga automática funciona ejecutando una devolución de llamada cuando se requiere una clase de PHP pero no se encuentra. Tales devoluciones de llamada generalmente intentan cargar estas clases.

En general, la carga automática puede entenderse como el intento de cargar archivos PHP (especialmente archivos de clase PHP, donde un archivo fuente PHP está dedicado para una clase específica) desde rutas apropiadas de acuerdo con el nombre completo de la clase (FQN) cuando se necesita una clase .

Supongamos que tenemos estas clases:

Archivo de clase para application\controllers\Base :

<?php
namespace application\controllers { class Base {...} }

Archivo de clase para application\controllers\Control :

<?php
namespace application\controllers { class Control {...} }

Archivo de clase para application\models\Page :

<?php
namespace application\models { class Page {...} }

Bajo la carpeta de origen, estas clases deben colocarse en las rutas como sus FQN respectivamente:

  • Carpeta de origen
    • applications
      • controllers
        • Base.php
        • Control.php
      • models
        • Page.php

Este enfoque hace posible resolver mediante programación la ruta del archivo de clase de acuerdo con el FQN, utilizando esta función:

function getClassPath(string $sourceFolder, string $className, string $extension = ".php") {
    return $sourceFolder . "/" . str_replace("\\", "/", $className) . $extension; // note that "/" works as a directory separator even on Windows
}

La función spl_autoload_register nos permite cargar una clase cuando sea necesario utilizando una función definida por el usuario:

const SOURCE_FOLDER = __DIR__ . "/src";
spl_autoload_register(function (string $className) {
    $file = getClassPath(SOURCE_FOLDER, $className);
    if (is_readable($file)) require_once $file;
});

Esta función se puede ampliar aún más para utilizar métodos de carga alternativos:

const SOURCE_FOLDERS = [__DIR__ . "/src", "/root/src"]);
spl_autoload_register(function (string $className) {
    foreach(SOURCE_FOLDERS as $folder) {
        $extensions = [
            // do we have src/Foo/Bar.php5_int64?
            ".php" . PHP_MAJOR_VERSION . "_int" . (PHP_INT_SIZE * 8),
            // do we have src/Foo/Bar.php7?
            ".php" . PHP_MAJOR_VERSION,
            // do we have src/Foo/Bar.php_int64?
            ".php" . "_int" . (PHP_INT_SIZE * 8),
            // do we have src/Foo/Bar.phps?
            ".phps"
            // do we have src/Foo/Bar.php?
            ".php"
        ];
        foreach($extensions as $ext) {
            $path = getClassPath($folder, $className, $extension);
            if(is_readable($path)) return $path;
        }
    }
});

Tenga en cuenta que PHP no intenta cargar las clases siempre que se carga un archivo que utiliza esta clase. Puede cargarse en medio de un script, o incluso en funciones de apagado. Esta es una de las razones por las que los desarrolladores, especialmente aquellos que usan la carga automática, deben evitar reemplazar los archivos de origen en ejecución, especialmente en archivos phar.

Vinculación dinámica

El enlace dinámico, también conocido como invalidación de método, es un ejemplo de polimorfismo de tiempo de ejecución que se produce cuando varias clases contienen implementaciones diferentes del mismo método, pero el objeto sobre el que se llamará el método es desconocido hasta el tiempo de ejecución .

Esto es útil si una determinada condición dicta qué clase se utilizará para realizar una acción, donde la acción se denomina igual en ambas clases.

interface Animal {
    public function makeNoise();
}

class Cat implements Animal {
    public function makeNoise
    {
        $this->meow();
    }
    ...
}

class Dog implements Animal {
    public function makeNoise {
        $this->bark();
    }
    ...
}

class Person {
    const CAT = 'cat';
    const DOG = 'dog';

    private $petPreference;
    private $pet;

    public function isCatLover(): bool {
        return $this->petPreference == self::CAT;
    }

    public function isDogLover(): bool {
        return $this->petPreference == self::DOG;
    }

    public function setPet(Animal $pet) {
        $this->pet = $pet;
    }

    public function getPet(): Animal {
        return $this->pet;
    }
}

if($person->isCatLover()) {
    $person->setPet(new Cat());
} else if($person->isDogLover()) {
    $person->setPet(new Dog());
}

$person->getPet()->makeNoise();

En el ejemplo anterior, la clase Animal ( Dog|Cat ) que creará makeNoise se desconoce hasta el tiempo de ejecución, según la propiedad dentro de la clase User .

Método y visibilidad de la propiedad

Hay tres tipos de visibilidad que puede aplicar a los métodos ( funciones de clase / objeto ) y propiedades ( variables de clase / objeto ) dentro de una clase, que proporcionan control de acceso para el método o la propiedad a la que se aplican.

Puede leer extensamente sobre esto en la Documentación de PHP para Visibilidad OOP .

Público

La declaración de un método o una propiedad como public permite que se acceda al método o la propiedad mediante:

  • La clase que lo declaró.
  • Las clases que extienden la clase declarada.
  • Cualquier objeto externo, clases o código fuera de la jerarquía de clases.

Un ejemplo de este acceso public sería:

class MyClass {
    // Property
    public $myProperty = 'test';

    // Method
    public function myMethod() {
        return $this->myProperty;
    }
}

$obj = new MyClass();
echo $obj->myMethod();
// Out: test

echo $obj->myProperty;
// Out: test

Protegido

La declaración de un método o una propiedad como protected permite que se pueda acceder al método o la propiedad mediante:

  • La clase que lo declaró.
  • Las clases que extienden la clase declarada.

Esto no permite que los objetos, clases o códigos externos fuera de la jerarquía de clases accedan a estos métodos o propiedades. Si algo que utiliza este método / propiedad no tiene acceso a él, no estará disponible y se generará un error. Sólo las instancias del yo declarado (o subclases del mismo) tienen acceso a él.

Un ejemplo de este acceso protected sería:

class MyClass {
    protected $myProperty = 'test';

    protected function myMethod() {
        return $this->myProperty;
    }
}

class MySubClass extends MyClass {
    public function run() {
        echo $this->myMethod();
    }
}

$obj = new MySubClass();
$obj->run(); // This will call MyClass::myMethod();
// Out: test

$obj->myMethod(); // This will fail.
// Out: Fatal error: Call to protected method MyClass::myMethod() from context ''

El ejemplo anterior señala que solo puede acceder a los elementos protected dentro de su propio ámbito. Esencialmente: "Lo que hay en la casa solo se puede acceder desde dentro de la casa".


Privado

La declaración de un método o una propiedad como private permite que se acceda al método o la propiedad mediante:

  • La clase que lo declaró Only (no subclases).

Un método o propiedad private solo es visible y accesible dentro de la clase que lo creó.

Tenga en cuenta que los objetos del mismo tipo tendrán acceso a los demás miembros privados y protegidos, aunque no sean las mismas instancias.

class MyClass {
    private $myProperty = 'test';

    private function myPrivateMethod() {
        return $this->myProperty;
    }

    public function myPublicMethod() {
        return $this->myPrivateMethod();
    }

    public function modifyPrivatePropertyOf(MyClass $anotherInstance) {
        $anotherInstance->myProperty = "new value";
    }
}

class MySubClass extends MyClass {
    public function run() {
        echo $this->myPublicMethod();
    }

    public function runWithPrivate() {
        echo $this->myPrivateMethod();
    }
}

$obj = new MySubClass();
$newObj = new MySubClass();

// This will call MyClass::myPublicMethod(), which will then call
// MyClass::myPrivateMethod();
$obj->run(); 
// Out: test

    
$obj->modifyPrivatePropertyOf($newObj);

$newObj->run();
// Out: new value

echo $obj->myPrivateMethod(); // This will fail.
// Out: Fatal error: Call to private method MyClass::myPrivateMethod() from context ''

echo $obj->runWithPrivate(); // This will also fail.
// Out: Fatal error: Call to private method MyClass::myPrivateMethod() from context 'MySubClass'

Como se indicó, solo puede acceder al método / propiedad private desde su clase definida.

Llamar a un constructor padre al crear una instancia de un hijo

Un error común de las clases secundarias es que, si su padre y su hijo contienen un método constructor ( __construct() ), solo se ejecutará el constructor de la clase secundaria . Puede haber ocasiones en las que necesite ejecutar el __construct() padre __construct() desde su hijo. Si necesita hacer eso, entonces deberá usar el resolutor parent:: scope:

parent::__construct();

Ahora aprovechar que en una situación del mundo real se vería algo así como:

class Foo {

    function __construct($args) { 
        echo 'parent'; 
    }

}

class Bar extends Foo {

    function __construct($args) {
        parent::__construct($args);
    }
}

Lo anterior ejecutará el elemento principal __construct() y el echo se ejecutará.

Palabra clave final

Def: Final Keyword evita que las clases secundarias invaliden un método prefijando la definición con final. Si la clase en sí se está definiendo como final, entonces no se puede extender

Método final

class BaseClass {
   public function test() {
       echo "BaseClass::test() called\n";
   }
   
   final public function moreTesting() {
       echo "BaseClass::moreTesting() called\n";
   }
}

class ChildClass extends BaseClass {
   public function moreTesting() {
       echo "ChildClass::moreTesting() called\n";
   }
}
// Results in Fatal error: Cannot override final method BaseClass::moreTesting()

Clase final

final class BaseClass {
   public function test() {
       echo "BaseClass::test() called\n";
   }

   // Here it doesn't matter if you specify the function as final or not
   final public function moreTesting() {
       echo "BaseClass::moreTesting() called\n";
   }
}

class ChildClass extends BaseClass {
}
// Results in Fatal error: Class ChildClass may not inherit from final class (BaseClass)

Constantes finales: a diferencia de Java, la palabra clave final no se usa para las constantes de clase en PHP. Utilice la palabra clave const lugar.

¿Por qué tengo que usar final ?

  1. La prevención de la cadena de herencia masiva de la fatalidad
  2. Composición estimulante
  3. Forzar al desarrollador a pensar en la API pública del usuario
  4. Forzar al desarrollador a reducir la API pública de un objeto
  5. Una clase final siempre se puede hacer extensible.
  6. extends roturas de encapsulación.
  7. No necesitas esa flexibilidad.
  8. Eres libre de cambiar el código

Cuándo evitar final : las clases finales solo funcionan de manera efectiva bajo los siguientes supuestos:

  1. Hay una abstracción (interfaz) que implementa la clase final
  2. Toda la API pública de la clase final es parte de esa interfaz

$ esto, auto y estático más el singleton

Use $this para referirse al objeto actual. Use self para referirse a la clase actual. En otras palabras, use $this->member para $this->member no estáticos, use self::$member para miembros estáticos.

En el siguiente ejemplo, sayHello() y sayGoodbye() están usando self y $this diferencia se puede observar aquí.

class Person {
    private $name;

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

    public function getName() {
        return $this->name;
    }

    public function getTitle() {
        return $this->getName()." the person";
    }

    public function sayHello() {
        echo "Hello, I'm ".$this->getTitle()."<br/>";
    }

    public function sayGoodbye() {
        echo "Goodbye from ".self::getTitle()."<br/>";
    }
}

class Geek extends Person {
    public function __construct($name) {
        parent::__construct($name);
    }

    public function getTitle() {
        return $this->getName()." the geek";
    }
}

$geekObj = new Geek("Ludwig");
$geekObj->sayHello();
$geekObj->sayGoodbye();

static refiere a cualquier clase en la jerarquía en la que llamaste al método. Permite una mejor reutilización de las propiedades de clase estáticas cuando las clases se heredan.

Considere el siguiente código:

class Car {
    protected static $brand = 'unknown';
    
    public static function brand() {
         return self::$brand."\n";
    }
}

class Mercedes extends Car {
    protected static $brand = 'Mercedes';
}

class BMW extends Car {
    protected static $brand = 'BMW';
}

echo (new Car)->brand();
echo (new BMW)->brand();
echo (new Mercedes)->brand();

Esto no produce el resultado que desea:

desconocido
desconocido
desconocido

Esto se debe a que self refiere a la clase Car cuando se llama a la brand() método brand() .

Para referirse a la clase correcta, necesita usar static lugar:

class Car {
    protected static $brand = 'unknown';
    
    public static function brand() {
         return static::$brand."\n";
    }
}

class Mercedes extends Car {
    protected static $brand = 'Mercedes';
}

class BMW extends Car {
    protected static $brand = 'BMW';
}

echo (new Car)->brand();
echo (new BMW)->brand();
echo (new Mercedes)->brand();

Esto produce el resultado deseado:

desconocido
BMW
Mercedes

Véase también Enlace estático tardío

El singleton

Si tiene un objeto que es costoso crear o representa una conexión a algún recurso externo que desea reutilizar, es decir, una conexión de base de datos donde no existe una agrupación de conexiones o un socket para algún otro sistema, puede usar las palabras clave static y self en un clase para hacer un singleton. Hay opiniones fuertes acerca de si el patrón de singleton debe o no debe usarse, pero tiene sus usos.

class Singleton {
    private static $instance = null;

    public static function getInstance(){
        if(!isset(self::$instance)){
            self::$instance = new self();
        }
        
        return self::$instance;
    }
    
    private function __construct() {
        // Do constructor stuff
    }
}

Como puede ver en el código de ejemplo, estamos definiendo una $instance privada de propiedad estática $instance para contener la referencia del objeto. Como esto es estático, esta referencia se comparte entre TODOS los objetos de este tipo.

El método getInstance() utiliza un método conocido como creación de instancias perezosa para retrasar la creación del objeto hasta el último momento posible, ya que no desea tener objetos no utilizados en la memoria que nunca se pretendió usar. También ahorra tiempo y CPU en la carga de la página, no tiene que cargar más objetos de los necesarios. El método comprueba si el objeto está establecido, lo crea, si no, y lo devuelve. Esto asegura que solo un objeto de este tipo sea creado.

También estamos configurando el constructor para que sea privado para garantizar que nadie lo cree con la new palabra clave desde el exterior. Si necesita heredar de esta clase, simplemente cambie las palabras clave private a protected .

Para usar este objeto solo escribe lo siguiente:

$singleton = Singleton::getInstance();

Ahora le imploro que use la inyección de dependencia donde pueda y apunte a objetos acoplados de forma flexible, pero a veces eso no es razonable y el patrón de singleton puede ser de utilidad.

Autocarga

Nadie quiere require o include cada vez que se usa una clase o herencia. Debido a que puede ser doloroso y es fácil de olvidar, PHP está ofreciendo el llamado autoloading. Si ya está utilizando Composer, lea acerca de la carga automática utilizando Composer .

¿Qué es exactamente la carga automática?

El nombre básicamente lo dice todo. No tiene que obtener el archivo donde se almacena la clase solicitada, pero PHP lo carga automáticamente .

¿Cómo puedo hacer esto en PHP básico sin código de terceros?

Existe la función __autoload , pero se considera una mejor práctica usar spl_autoload_register . PHP considerará estas funciones cada vez que no se defina una clase dentro del espacio dado. Entonces, agregar la carga automática a un proyecto existente no es un problema, ya que las clases definidas (a través de require ie) funcionarán como antes. En aras de la precisión, los siguientes ejemplos usarán funciones anónimas, si usa PHP <5.3, puede definir la función y pasar su nombre como argumento a spl_autoload_register .

Ejemplos

spl_autoload_register(function ($className) {
    $path = sprintf('%s.php', $className);
    if (file_exists($path)) {
        include $path;
    } else {
        // file not found
    }
});

El código anterior simplemente intenta incluir un nombre de archivo con el nombre de la clase y la extensión anexada ".php" usando sprintf . Si FooBar necesita ser cargado, parece que FooBar.php existe y si es así lo incluye.

Por supuesto, esto puede extenderse para adaptarse a las necesidades individuales del proyecto. Si _ dentro de un nombre de clase se usa para agrupar, por ejemplo, User_Post y User_Image refieren a User , ambas clases se pueden mantener en una carpeta llamada "User" como:

spl_autoload_register(function ($className) {
    //                        replace _ by / or \ (depending on OS)
    $path = sprintf('%s.php', str_replace('_', DIRECTORY_SEPARATOR, $className) );
    if (file_exists($path)) {
        include $path;
    } else {
        // file not found
    }
});

La clase User_Post ahora se cargará desde "User / Post.php", etc.

spl_autoload_register puede adaptarse a diversas necesidades. Todos sus archivos con clases se llaman "class.CLASSNAME.php"? No hay problema. Varios anidamientos ( User_Post_Content => "User / Post / Content.php")? No hay problema tampoco.

Si desea un mecanismo de carga automática más elaborado, y aún no desea incluir Composer, puede trabajar sin agregar bibliotecas de terceros.

spl_autoload_register(function ($className) {
    $path = sprintf('%1$s%2$s%3$s.php',
        // %1$s: get absolute path
        realpath(dirname(__FILE__)),
        // %2$s: / or \ (depending on OS)
        DIRECTORY_SEPARATOR,
        // %3$s: don't wory about caps or not when creating the files
        strtolower(
            // replace _ by / or \ (depending on OS)
            str_replace('_', DIRECTORY_SEPARATOR, $className)
        )
    );

    if (file_exists($path)) {
        include $path;
    } else {
        throw new Exception(
            sprintf('Class with name %1$s not found. Looked in %2$s.',
                $className,
                $path
            )
        );
    }
});

Usando autocargadores como este, felizmente puede escribir código como este:

require_once './autoload.php'; // where spl_autoload_register is defined

$foo = new Foo_Bar(new Hello_World());

Utilizando clases:

class Foo_Bar extends Foo {}
class Hello_World implements Demo_Classes {}

Estos ejemplos incluirán clases de foo/bar.php , foo.php , hello/world.php y demo/classes.php .

Clases anonimas

Se introdujeron clases anónimas en PHP 7 para permitir la creación fácil de objetos únicos y rápidos. Pueden tomar argumentos de constructor, extender otras clases, implementar interfaces y usar rasgos al igual que las clases normales.

En su forma más básica, una clase anónima se parece a lo siguiente:

new class("constructor argument") {
    public function __construct($param) {
        var_dump($param);
    }
}; // string(20) "constructor argument"

Anidar una clase anónima dentro de otra clase no le da acceso a métodos o propiedades privadas o protegidas de esa clase externa. El acceso a los métodos protegidos y las propiedades de la clase externa se puede obtener extendiendo la clase externa de la clase anónima. El acceso a las propiedades privadas de la clase externa se puede obtener pasándolas al constructor de la clase anónima.

Por ejemplo:

class Outer {
    private $prop = 1;
    protected $prop2 = 2;

    protected function func1() {
        return 3;
    }

    public function func2() {
        // passing through the private $this->prop property
        return new class($this->prop) extends Outer {
            private $prop3;

            public function __construct($prop) {
                $this->prop3 = $prop;
            }

            public function func3() {
                // accessing the protected property Outer::$prop2
                // accessing the protected method Outer::func1()
                // accessing the local property self::$prop3 that was private from Outer::$prop
                return $this->prop2 + $this->func1() + $this->prop3;
            }
        };
    }
}

echo (new Outer)->func2()->func3(); // 6

Definiendo una clase básica

Un objeto en PHP contiene variables y funciones. Los objetos normalmente pertenecen a una clase, que define las variables y funciones que contendrán todos los objetos de esta clase.

La sintaxis para definir una clase es:

class Shape {
    public $sides = 0;
    
    public function description() {
        return "A shape with $this->sides sides.";
    }
}

Una vez que se define una clase, puede crear una instancia usando:

$myShape = new Shape();

A las variables y funciones en el objeto se accede de esta manera:

$myShape = new Shape();
$myShape->sides = 6;

print $myShape->description(); // "A shape with 6 sides"

Constructor

Las clases pueden definir un __construct() especial __construct() , que se ejecuta como parte de la creación del objeto. Esto se usa a menudo para especificar los valores iniciales de un objeto:

class Shape {
    public $sides = 0;
    
    public function __construct($sides) {
        $this->sides = $sides;
    }
    
    public function description() {
        return "A shape with $this->sides sides.";
    }
}

$myShape = new Shape(6);

print $myShape->description(); // A shape with 6 sides

Extendiendo otra clase

Las definiciones de clase pueden extender las definiciones de clase existentes, agregar nuevas variables y funciones, así como modificar aquellas definidas en la clase principal.

Aquí hay una clase que amplía el ejemplo anterior:

class Square extends Shape {
    public $sideLength = 0;
    
    public function __construct($sideLength) {
       parent::__construct(4);
       
       $this->sideLength = $sideLength;
    }
    
    public function perimeter() {
        return $this->sides * $this->sideLength;
    }

    public function area() {
        return $this->sideLength * $this->sideLength;
    }
}

La clase Square contiene variables y comportamiento tanto para la clase Shape como para la clase Square :

$mySquare = new Square(10);

print $mySquare->description()/ // A shape with 4 sides

print $mySquare->perimeter() // 40

print $mySquare->area() // 100


Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow