010 Классы и объекты
Zephir позволяет создавать только классы, однако также становится возможным использовать исключения, вмето фатальных ошибок, или предупреждений.
Классы
Каждый файл в Zephir должны содержать класс или интерфейс (причем только один). Структура класса крайне схожа с структурой обьявления его в PHP:
namespace Test; /** * Это пример класса */ class MyClass { }
Модификаторы Классов
Поддерживаются следующие модификаторы классов:
Final: если класс имеет этот модификатор, он не может быть расширен:
namespace Test; /** * Этот класс не может быть расширен другим классом */ final class MyClass { }
Abstract: Если класс имеет этот модификатор,им нельзя создать объект:
namespace Test; /** * Невозможно создать экземпляр этого класса */ abstract class MyClass { }
Реализация интерфейсов
Классы Zephir могут реализовывать любое количество интерфейсов при условии, что эти интерфейсы видимы ( visible
) для использования классом. Однако, есть случаи, когда класс Zephir (и впоследствии расширение) может потребоваться для реализации интерфейса, который создан в другом расширении.
Если мы хотим реализовать MiddlewareInterface
из расширения PSR
, нам нужно создать интерфейс stub
:
// middlewareinterfaceex.zep namespace Test\Oo\Extend; use Psr\Http\Server\MiddlewareInterface; interface MiddlewareInterfaceEx extends MiddlewareInterface { }
Отсюда мы можем использовать интерфейс stub
в нашем расширении.
/** * @test */ public function shouldExtendMiddlewareInterface() { if (!extension_loaded('psr')) { $this->markTestSkipped( "Расширение psr не загружено" ); } $this->assertTrue( is_subclass_of(MiddlewareInterfaceEx::class, 'Psr\Http\Server\MiddlewareInterface') ); }
ПРИМЕЧАНИЕ Ответственность за обеспечение наличия всех внешних ссылок перед загрузкой расширения лежит на разработчику. Таким образом, для приведенного выше примера необходимо загрузить расширение PSR, прежде чем загружать встроенное расширение Zephir.
Реализация методов
Ключевое слово “function” декларирует новый метод. Методы имеют те же модификаторы для разрешения видимости, что и PHP, однако Zephir требует явно его указывать.
namespace Test; class MyClass { public function myPublicMethod() { // ... } protected function myProtectedMethod() { // ... } private function myPrivateMethod() { // ... } }
Методы могут принимать как обязательные так и необязательные параметры:
namespace Test; class MyClass { /** * Все параметры обязательные */ public function doSum1(a, b) { return a + b; } /** * Обязателен только 'a', 'b' не обязателен и имеет значение по умолчанию */ public function doSum2(a, b=3) { return a + b; } /** * Оба параметра не обязательны */ public function doSum3(a=1, b=2) { return a + b; } /** * Параметры обязательны, и они должны быть целочисленными */ public function doSum4(int a, int b) { return a + b; } /** * Параметры обязательны, целочисленны и имеют значения по умолчанию */ public function doSum4(int a=4, int b=2) { return a + b; } }
Необязательные параметры, допускающие значение NULL
Zephir гарантирует, что значение переменной останется того типа, которым она была объявлена. Это заставляет Zephir преобразовать значение null в ближайшее приблизительное значение:
public function foo(int a = null) { echo a; // если "a " не передан, выводится 0 } public function foo(boolean a = null) { echo a; // если "a" не передан, выводится false } public function foo(string a = null) { echo a; // если "a" не передан, выводится пустая строка } public function foo(array a = null) { var_dump(a); // если "a" не передан, выводится пустой массив }
Поддерживаемые видимости метода (Инкапсуляция)
- Public: Методы с модификатором “public” экспортируются в расширение, это значит, что этими методами можно пользоваться также, как и самому расширению.
- Protected: Методы с модификатором “protected” экспортируются в расширение, это значит, что этими методами можно пользоваться также, как и самому расширению. Однако, защищенные методы могут быть вызваны либо внутри класса, либо наследником класса.
- Private: Методы с модификатором “private” не экспортируются в расширение, это значит, что эти методы может использовать только класс, в котором метод реализован.
Поддерживаемые модификаторы
- Static: Методы с этим модификатором можно вызывать только в статическом контексте (из класса, а не объекта).
- Final: Если метод имеет этот модификатор, он не может быть переопределен.
- Deprecated: Методы с модификатором “deprecated” - устаревший бросают ошибку (E_DEPRECATED) где они вызываются.
Сокращения для геттеров/сеттеров
Как в C#, вы можете использовать сокращения для get/set/toString методов. Это значит, что вы можете создавать геттеры и сеттеры для свойств не явно.
Рассмотрим как выглядит код без сокращений:
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; } }
Вы можете написать тот же код, используя сокращения:
namespace App; class MyClass { protected myProperty { set, get, toString }; protected someProperty = 10 { set, get }; }
Zephir сгенерирует реальные методы, но вам не прийдется писать их самостоятельно.
Тип возвращаемого значения
Методы классов и интерфейсов могут объявлять тип возвращаемого значения. Это поможет компилятору подсказать вам о ошибках в расширении. Рассмотрим пример:
namespace App; class MyClass { public function getSomeData() -> string { // здесь будет сгенерирована ошибка // потому что возвращаемое значение (boolean) // не соответсвует ранее объявленному типу return false; } public function getSomeOther() -> <App\MyInterface> { // здесь будет сгенерирована ошибка // если возвращаемый объект не реализует // ожидаемый компилитором интерфейс return new App\MyObject; } public function process() { var myObject; // подсказка типа сообщит компилятору, // что myObject является экземпляром класса, // реализующего App\MyInterface let myObject = this->getSomeOther(); // компилятор проверит, реализует ли App\MyInterface // метод с именем "someMethod" echo myObject->someMethod(); } }
Метод может содержать более одного возвращаемого типа. Для объявления нескольких типов возвращаемых значений, разделите их оператором ‘|’.
namespace App; class MyClass { public function getSomeData(a) -> string|bool { if a == false { return false; } return "error"; } }
Возвращаемый тип: void
Методы также могут быть помечены как ‘void’. Это значит, что метод не можете вернуть ничего.
public function setConnection(connection) -> void { let this->_connection = connection; }
Почему это полезно? Потому что если компилятор обнаружит, что программа использует возвращаемое значение, то сгенерирует исключение.
let myDb = db->setConnection(connection); myDb->execute(“SELECT * FROM robots”); // тут сгенерируется исключение
Строгие/Приводимые Типы Параметров
В Zephir вы можете определить тип для каждого параметра в методе. По умолчанию все типизированные аргументы приводимы. Это зачит, что если значение не соответсвует ожидаемому типу, Zephir сгенерирует код для его приведения к ожидаемому.
public function filterText(string text, boolean escape=false) { //... }
Этот метод будет работать так:
<?php $o->filterText(1111, 1); // OK $o->filterText("some text", null); // OK $o->filterText(null, true); // OK $o->filterText("some text", true); // OK $o->filterText(array(1, 2, 3), true); // Ошибка
Однако, передача не правильного типа часто может вести к багам. Вы можете запретить автоматическое приведение, используя строгие типы:
public function filterText(string! text, boolean escape=false) { //... }
Теперь большинство вызовов сгенерируют исключения благодяря неправильному переданному типу:
<?php $o->filterText(1111, 1); // Ошибка $o->filterText("some text", null); // OK $o->filterText(null, true); // Ошибка $o->filterText("some text", true); // OK $o->filterText(array(1, 2, 3), true); // Ошибка
Определяя какие параметры строгие, а какие приводимые, вы можете контролировать поведение так, этого хочет.
Read-Only Параметры
Используя ключевое слово ‘const’ вы можете объявить, что параметр только для чтения. Такие параметры не могут быть изменены внутри метода:
namespace App; class MyClass { // "a" только для чтения public function getSomeData(const string a) { // компилятор сгенерирует ошибку let a = "hello"; } }
Когда параметр объявлен только для чтения, компилятор может безопасно создавать оптимизации над этими переменными.
Реализация свойств
Переменные-члены класса называются “properties”. По умолчанию они действуют как свойства PHP. Свойства экспортируются в расширение PHP и являются видимыми из кода PHP. Свойства реализуют обычные модификаторы видимости, доступные в PHP, явно задавать модификатор видимости обязательно в Zephir:
namespace Test; class MyClass { public myProperty1; protected myProperty2; private myProperty3; }
В пределах методов класса нестатические свойства могут быть доступны с помощью -> (Оператор объекта): this->property (где property - это имя свойства):
namespace Test; class MyClass { protected myProperty; public function setMyProperty(var myProperty) { let this->myProperty = myProperty; } public function getMyProperty() { return this->myProperty; } }
Свойства могут иметь литерально совместимые значения по умолчанию. Эти значения должны быть в состоянии быть оценены во время компиляции и не должны зависеть от информации времени выполнения для оценки:
namespace Test; class MyClass { protected myProperty1 = null; protected myProperty2 = false; protected myProperty3 = 2.0; protected myProperty4 = 5; protected myProperty5 = "my value"; }
Обновление свойств
Свойства можно обновить, обратившись к ним с помощью оператора ‘->’:
let this->myProperty = 100;
Zephir проверяет, что свойства существуют, когда программа обращается к ним, если свойство не объявлено, вы получите исключение компилятора:
CompilerException: Property '_optionsx' is not defined on class 'App\MyClass' in /Users/scott/cphalcon/phalcon/cache/backend.zep on line 62 let this->_optionsx = options; ------------^
Если вы хотите избежать валидации компилятора или просто создать свойство динамически, вы можете заключить имя свойства, используя строковые кавычки:
let this->{"myProperty"} = 100;
Вы также можете использовать простую переменную для обновления свойства, имя свойства будет взято из переменной:
let someProperty = "myProperty"; let this->{someProperty} = 100;
Чтение свойств
Свойства можно прочитать, обратившись к ним с помощью оператора ‘->’:
echo this->myProperty;
Как и при обновлении, свойства могут динамически читаться следующим образом:
// Избежание проверки компилятора или чтение динамического пользовательского свойства echo this->{"myProperty"}; //Чтение с использованием имени переменной let someProperty = "myProperty"; echo this->{someProperty}
Константы классов
Класс может содержать константы класса, которые остаются неизменными и неизменными после компиляции расширения. Константы класса экспортируются в расширение PHP, что позволяет им использовать их с PHP.
namespace Test; class MyClass { const MYCONSTANT1 = false; const MYCONSTANT2 = 1.0; }
К константам класса можно получить доступ, используя имя класса и статический оператор (: :):
namespace Test; class MyClass { const MYCONSTANT1 = false; const MYCONSTANT2 = 1.0; public function someMethod() { return MyClass::MYCONSTANT1; } }
Взов Методов
Методы можно вызывать, используя оператор объекта (->), как в PHP:
namespace Test; class MyClass { protected function _someHiddenMethod(a, b) { return a - b; } public function someMethod(c, d) { return this->_someHiddenMethod(c, d); } }
Статические методы должны вызываться с использованием статического оператора (: :):
namespace Test; class MyClass { protected static function _someHiddenMethod(a, b) { return a - b; } public static function someMethod(c, d) { return self::_someHiddenMethod(c, d); } }
Вы можете вызвать методы динамическим способом следующим образом:
namespace Test; class MyClass { protected adapter; public function setAdapter(var adapter) { let this->adapter = adapter; } public function someMethod(var methodName) { return this->adapter->{methodName}(); } }
Доступ к параметрам по имени
Zephir поддерживает вызов параметров метода по имени или аргументам ключевых слов. Именованные параметры могут быть полезны, если требуется передать параметры в произвольном порядке, задокументировать значение параметров или задать параметры более элегантным способом.
Рассмотрим следующий пример. Класс С именем “Image " имеет метод, который получает четыре параметра:
namespace Test; class Image { public function chop(width = 600, height = 400, x = 0, y = 0) { //... } }
Использование стандартного метода вызова:
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
Используя именованные параметры, вы можете:
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
Когда компилятор (во время компиляции) не знает правильного порядка этих параметров, они должны быть разрешены во время выполнения. В этом случае возможны минимальные дополнительные накладные расходы:
let i = new {someClass}(); i->chop(y: 30, x: 20);