Clases y Objetos

Zephir promueve la programación orientada a objetos. Por esta razón sólo puedes exportar métodos y clases en las extensiones. También verá que la mayoría de las veces, los errores de tiempo de ejecución arrojan excepciones en lugar de errores fatales o advertencias.

Clases

Cada archivo de Zephir debe implementar una clase o una interfaz (y sólo una). Una estructura de clase es muy similar a una clase en PHP:

namespace Test;

/**
 * Esta es una clase de ejemplo
 */
class MyClass
{

}

Modificadores de Clase

Son soportadas los siguientes modificadores de clase:

final: Si una clase tiene este modificador no puede ser extendida:

namespace Test;

/**
 * Esta clase no puede se extendida por otras clases
 */
final class MyClass
{

}

abstract: Si una clase tiene este modificador no puede ser instanciada:

namespace Test;

/**
 * Esta clase no puede ser instanciada
 */
abstract class MyClass
{

}

Implementación de Interfaces

Las clases de Zephir pueden implementar cualquier cantidad de interfaces, siempre que estas clases sean visible para que la clase las utilice. Sin embargo, hay ocasiones en que la clase Zephir (y posteriormente la extensión) puede requerir implementar una interfaz que se construya en una extensión diferente.

Si quieren implementar MiddlewareInterface de la extensión PSR, deberemos crear una interface stub:

// middlewareinterfaceex.zep
namespace Test\Oo\Extend;

use Psr\Http\Server\MiddlewareInterface;

interface MiddlewareInterfaceEx extends MiddlewareInterface
{

}

Desde aquí podemos utilizar la interfaz stub a lo largo de nuestra extensión.

/**
 * @test
 */
public function shouldExtendMiddlewareInterface()
{
    if (!extension_loaded('psr')) {
        $this->markTestSkipped(
            "La extensión PSR no esta cargada"
        );
    }

    $this->assertTrue(
        is_subclass_of(MiddlewareInterfaceEx::class, 'Psr\Http\Server\MiddlewareInterface')
    );
}

NOTA Es responsabilidad del desarrollador asegurarse de que todas las referencias externas estén presentes antes de que se cargue la extensión. Entonces para el ejemplo anterior, la extensión PSR tiene que estar cargada antes que la extensión construida en Zephir este cargada.

Implementación de Métodos

La palabra clave function introduce un método. Los métodos implementan los modificadores de visibilidad generalmente disponibles en PHP. Establecer explícitamente un modificador de visibilidad es obligatorio en Zephir:

namespace Test;

class MyClass
{

    public function myPublicMethod()
    {
        // ...
    }

    protected function myProtectedMethod()
    {
        // ...
    }

    private function myPrivateMethod()
    {
        // ...
    }
}

Los métodos pueden recibir parámetros obligatorios y opcionales:

namespace Test;

class MyClass
{

    /**
     * Todos los parámetros son obligatorios
     */
    public function doSum1(a, b)
    {
        return a + b;
    }

    /**
     * Solo 'a' es obligatorio, 'b' es opcional y tiene un valor por defecto
     */
    public function doSum2(a, b = 3)
    {
        return a + b;
    }

    /**
     * Ambos parámetros son opcionales
     */
    public function doSum3(a = 1, b = 2)
    {
        return a + b;
    }

    /**
     * Los parámetros son obligatorios y sus valores deben ser del tipo integer
     */
    public function doSum4(int a, int b)
    {
        return a + b;
    }

    /**
     * Tipos estáticos con valores por defecto
     */
    public function doSum4(int a = 4, int b = 2)
    {
        return a + b;
    }
}

Parámetros opcionales nulos

Zephir se asegura que el valor de una variable permanezca del tipo que fue declarada la variable. Esto hace que Zephir convierta el valor null al valor más cercado:

public function foo(int a = null)
{
    echo a; // si no es pasado "a" imprime 0
}

public function foo(boolean a = null)
{
    echo a; // si no es pasado "a" imprime false
}

public function foo(string a = null)
{
    echo a; // si no es pasado "a" imprime una cadena de texto vacía
}

public function foo(array a = null)
{
    var_dump(a); // si no es pasado "a" imprime un array vacío
}

Visibilidades soportadas

  • Público: los métodos marcados como public se exportan a la extensión PHP; esto significa que métodos públicos son accesibles para el código PHP y para la extensión.

  • Protegido: los métodos marcados como protected se exportan a la extensión PHP; esto significa que métodos protegidos son accesibles para el código PHP y para la extensión. Sin embargo, sólo se pueden llamar estos métodos protegidos en el ámbito de la clase o en clases que la heredan.

  • Privado: los métodos marcados como private no se exportan a la extensión PHP; esto significa que métodos privados sólo son accesibles a la clase donde está implementados.

Modificadores Soportados

  • static: los métodos con este modificador sólo se pueden llamar en un contexto estático (de la clase, no de un objeto).

  • final: si un método tiene este modificador no se puede sobre cargar.

  • deprecated: los métodos que se marcan como deprecated arrojan un error E_DEPRECATED cuando se les llama.

Métodos abreviados de getter/setter

Al igual que en C#, es posible utilizar en Zephir los atajos get/set/toString. Esta característica le permite escribir fácilmente setters y getters para las propiedades, sin implementar explícitamente los métodos como tales.

Por ejemplo, sin atajos necesitaríamos un código como el siguiente:

namespace Test;

class MyClass
{
    protected myProperty;

    protected someProperty = 10;

    public function setMyProperty(myProperty)
    {
        let this->myProperty = myProperty;
    }

    public function getMyProperty()
    {
        return this->myProperty;
    }

    public function setSomeProperty(someProperty)
    {
        let this->someProperty = someProperty;
    }

    public function getSomeProperty()
    {
        return this->someProperty;
    }

    public function __toString()
    {
        return this->myProperty;
    }
}

Es posible escribir el mismo código utilizando los siguiente métodos abreviados:

namespace App;

class MyClass
{
    protected myProperty {
        set, get, toString
    };

    protected someProperty = 10 {
        set, get
    };
}

Cuando el código es compilado, estos métodos son exportados como métodos reales, pero usted no tiene que escribirlos manualmente.

Tipo de valor devuelto

Los métodos en clases e interfaces pueden tener "sugerencias de tipo de retorno". Estos proporcionarán información adicional útil al compilador para informarle sobre los errores en su aplicación. Considere el siguiente ejemplo:

namespace App;

class MyClass
{
    public function getSomeData() -> string
    {
        // esto lanzará una excepción del compilador
        // ya que el valor devuelto (booleano) no coincide
        // la cadena de tipo devuelta esperada
        return false;
    }

    public function getSomeOther() -> <App\MyInterface>
    {
        // esto lanzará una excepción del compilador
        // si el objeto devuelto no implementa
        // la interfaz esperada App\MyInterface
        return new App\MyObject;
    }

    public function process()
    {
        var myObject;

        // la sugerencia de tipo le dirá al compilador que
        // myObject es una instancia de una clase
        // que implementa App\MyInterface
        let myObject = this->getSomeOther();

        // el compilador comprobará si App\MyInterface
        // implementa un método llamado "someMethod"
        echo myObject->someMethod();
    }
}

Un método puede tener más de un tipo de valor devuelto. Cuando se definen varios tipos, debe utilizarse el operador | para separar estos tipos.

namespace App;

class MyClass
{
    public function getSomeData(a) -> string | bool
    {
        if a == false {
            return false;
        }
        return "error";
    }
}

Tipo de valor devuelto: void

Los métodos también se pueden marcar como void. Esto significa que un método no puede devolver datos:

public function setConnection(connection) -> void
{
    let this->_connection = connection;
}

¿Por qué esto es útil? Porque el compilador puede detectar si el programa espera un valor devuelto de estos métodos y producir una excepción del compilador:

let myDb = db->setConnection(connection); // esto producirá una excepción
myDb->execute("SELECT * FROM robots");

Tipos de datos de parámetro Estricto/Flexible

En Zephir, puede especificar el tipo de datos de cada parámetro de un método. Por defecto, estos tipos de datos son flexibles; esto significa que si se pasa un valor con un tipo de datos equivocado (pero compatible), Zephir intentará convertir de forma transparente al tipo esperado:

public function filterText(string text, boolean escape=false)
{
    //...
}

El método anterior funcionará con las siguientes llamadas:

<?php

$o->filterText(1111, 1);              // OK
$o->filterText("un texto", null);    // OK
$o->filterText(null, true);           // OK
$o->filterText("un texto", true);    // OK
$o->filterText(array(1, 2, 3), true); // FALLO

Sin embargo, pasando un tipo incorrecto, a menudo, podría conducir a errores. El uso incorrecto de una API específica produciría resultados inesperados. Puede no permitir la conversión automática del parámetro con un tipo de datos estricto:

public function filterText(string! text, boolean escape=false)
{
    //...
}

Ahora, la mayoría de las llamadas con un tipo incorrecto causarán una excepción debido a los tipos de datos no válidos pasados:

<?php

$o->filterText(1111, 1);              // FALLO
$o->filterText("un texto", null);    // OK
$o->filterText(null, true);           // FALLO
$o->filterText("un texto", true);    // OK
$o->filterText(array(1, 2, 3), true); // FALLO

Al especificar qué parámetros son estrictos y cuales pueden ser flexible, un desarrollador puede establecer el comportamiento específico que realmente desea.

Parámetros de sólo lectura

Utilizando la palabra clave const puede marcar los parámetros como de solo lectura, esto ayuda a respetar la correctitud de constantes. Los parámetros marcados con este atributo no pueden ser modificados dentro del método:

namespace App;

class MyClass
{
    // "a" es solo lectura
    public function getSomeData(const string a)
    {
        // esto arrojará una excepción del compilador
        let a = "hola";
    }
}

Cuando un parámetro es declarado como solo lectura, el compilador puede hacer suposiciones seguras y realizar futuras optimizaciones sobre estas variables.

Implementación de Propiedades

Las variables miembro de la clase se llaman "propiedades". Por defecto, estas actúan igual que las propiedades en PHP. Las propiedades se exportan a la extensión PHP y son accesibles desde el código PHP. Las propiedades implementan los modificadores de visibilidad generalmente disponibles en PHP, configurar explícitamente un modificador de visibilidad es obligatorio en Zephir:

namespace Test;

class MyClass
{
    public myProperty1;

    protected myProperty2;

    private myProperty3;
}

En métodos de la clase, las propiedades no estáticas se pueden acceder usando el operador ->:

namespace Test;

class MyClass
{

    protected myProperty;

    public function setMyProperty(var myProperty)
    {
        let this->myProperty = myProperty;
    }

    public function getMyProperty()
    {
        return this->myProperty;
    }
}

Las propiedades pueden tener valores literales compatibles por defecto compatible. Estos valores deben poder evaluarse en tiempo de compilación y no deben depender de la información de tiempo de ejecución para poder ser evaluados:

namespace Test;

class MyClass
{

    protected myProperty1 = null;
    protected myProperty2 = false;
    protected myProperty3 = 2.0;
    protected myProperty4 = 5;
    protected myProperty5 = "mi valor";
}

Actualización de propiedades

Las propiedades pueden actualizarse accediendo a ellas mediante el operador ->:

let this->myProperty = 100;

Zephir comprueba estas propiedades existan cuando un programa está accediendo a ellas. Si no se declara una propiedad, se obtendrá una excepción del compilador:

CompilerException: Property '_optionsx' is not defined on class 'App\MyClass' in /Users/scott/utils/app/myclass.zep on line 62

      let this->_optionsx = options;
      ------------^

Si desea evitar esta validación del compilador, o crear una propiedad dinámicamente, puede incluir el nombre de la propiedad utilizando llaves y comillas dobles:

let this->{"myProperty"} = 100;

También puede utilizar una simple variable para actualizar una propiedad; se tomara el nombre de la variable:

let someProperty = "myProperty";
let this->{someProperty} = 100;

Leyendo propiedades

Las propiedades pueden leerse accediendo a ellas mediante el operador ->:

echo this->myProperty;

Como al actualizar, se puede leer dinámicamente de esta manera:

// Evitar comprobación del compilador o leer dinámicamente una propiedad definida por el usuario
echo this->{"myProperty"};

// Leer utilizando el nombre de la variable
let someProperty = "myProperty";
echo this->{someProperty}

Constante de Clase

Las clases pueden contener constantes de clase que siguen siendo las mismas e inmutables una vez compilada la extensión. Las constantes de clase son exportadas a la extensión PHP, lo que les permite ser utilizadas desde PHP.

namespace Test;

class MyClass
{
    const MYCONSTANT1 = false;
    const MYCONSTANT2 = 1.0;
}

Las constantes de la clase se pueden acceder utilizando el nombre de la clase y el operador estático :::

namespace Test;

class MyClass
{

    const MYCONSTANT1 = false;
    const MYCONSTANT2 = 1.0;

    public function someMethod()
    {
        return MyClass::MYCONSTANT1;
    }
}

Llamando a métodos

Los métodos pueden ser llamados utilizando el operador de objectos -> como en PHP:

namespace Test;

class MyClass
{
    protected function _someHiddenMethod(a, b)
    {
        return a - b;
    }

    public function someMethod(c, d)
    {
        return this->_someHiddenMethod(c, d);
    }
}

Los métodos estáticos deben ser llamados utilizando el operador :::

namespace Test;

class MyClass
{
    protected static function _someHiddenMethod(a, b)
    {
        return a - b;
    }

    public static function someMethod(c, d)
    {
        return self::_someHiddenMethod(c, d);
    }
}

Puede llamar a los métodos en una forma dinámica como la siguiente:

namespace Test;

class MyClass
{
    protected adapter;

    public function setAdapter(var adapter)
    {
        let this->adapter = adapter;
    }

    public function someMethod(var methodName)
    {
        return this->adapter->{methodName}();
    }
}

Parámetros por Nombre

Zephir admite parámetros de método de llamada por nombre o argumentos de palabras clave. Los parámetros con nombre pueden ser útiles si desea pasar parámetros en un orden arbitrario, documentar el significado de los parámetros o especificar parámetros de una manera más elegante.

Considere el siguiente ejemplo. Una clase llamada Image tiene un método que recibe cuatro parámetros:

namespace Test;

class Image
{
    public function chop(width = 600, height = 400, x = 0, y = 0)
    {
        //...
    }
}

Utilizando el método estándar de llamado:

i->chop(100);             // width=100, height=400, x=0, y=0
i->chop(100, 50, 10, 20); // width=100, height=50, x=10, y=20

Utilizando parámetros nombrados, usted puede hacer lo siguiente:

i->chop(width: 100);              // width=100, height=400, x=0, y=0
i->chop(height: 200);             // width=600, height=200, x=0, y=0
i->chop(height: 200, width: 100); // width=100, height=200, x=0, y=0
i->chop(x: 20, y: 30);            // width=600, height=400, x=20, y=30

Cuando el compilador (en tiempo de compilación) no conoce el orden correcto de estos parámetros, estos deben resolverse en tiempo de ejecución. En este caso, podría haber una mínima sobrecarga adicional:

let i = new {someClass}();
i->chop(y: 30, x: 20);