Рубрики
Uncategorized

Эволюция создания экземпляров объектов PHP с подробной диагностикой ошибок

Описание безопасного метода создания экземпляра объекта. Помеченный php, oo php, шаблон.

Недавно я разработал новый метод (для меня) создания экземпляров объектов, который, как я думаю, я буду часто использовать в своих будущих разработках. Я сильно подозреваю, что это устоявшийся шаблон дизайна, у которого, вероятно, есть название, но я не получил квалификацию CS и придумал его сам.

Я говорю, что придумал это, но математики любят заявлять, как они открывают новую математику, что она уже существует в природе и структуре Вселенной, и аналогичным образом я подозреваю, что эта закономерность является естественной эволюцией, которая происходила раньше, несколько раз.

Частный конструктор с необязательным возвращаемым объектом

Большинство базовых классов объектов, которые я создаю, имеют частные конструкторы и предлагают один или несколько общедоступных методов создания экземпляров:

class token {
    public static function create(string $prop) : ?token {
        $token = null;
        if(is_valid($prop)){
            /* I use static routinely in case I want to extend 
            the class later */
            $token = new static();
            $token->prop = $prop;
        }
        return $token;
    }

    protected function __construct(){

    }
}

Круто, теперь, когда мы вызываем token::create('Foo') , мы можем получить объект токена или получить значение null. Это только начало.

Но что, если что-то пойдет не так? И мы хотим знать, что? НУЛЬ мало о чем нам говорит.

Кое-что, что я делал в прошлом, это передавал общий обработчик ошибок конструктору.

class token {
    public static function create(string $prop, errorHandler $errorHandler) : ?token {
        $token = null;
        if(is_valid($prop)){
            /* I use static routinely in case I want to extend the 
            class later */
            $token = new static();
            $token->prop = $prop;
        } else {
            $errorHandler->addError(errorHandler::INVALID_PROPERTY, $prop.' is not a valid string for some reason.');
        }
        return $token;
    }
    ...
}

Это немного лучше, мы можем предоставить нашему классу ErrorHandler любое количество констант ошибок и скомпилировать массив сообщений об ошибках для отправки пользователю.

Проблема в этом двоякая. Если метод ::create имеет более нескольких параметров, разработчики должны передать значения по умолчанию для всех промежуточных свойств, прежде чем они смогут добавить объект обработчика ошибок, если только мы не разместим его заранее, я полагаю; и, во-вторых, мы должны создать объект обработчика ошибок до создания объекта, который мы действительно хотим, каждый раз. Это добавляет шаблонные накладные расходы к каждому созданию экземпляра объекта, которых я бы предпочел избежать.

Представление объекта результата запроса

Метод ::create подразумевает, что он вроде как будет работать…что он вернет объект правильного типа. Добавление ? на подсказке типа, потому что все может пойти наперекосяк, вот где возникают проблемы.

Что, если бы мы могли быть уверены, что вернемся к объекту определенного типа? Мы могли бы “запросить” объект и обработать возвращаемый результат.

class token {
    public static function request(string $prop) : tokenRequestResult {
        $result = new tokenRequestResult();
        if(!is_valid($prop)){
            $result->addError(tokenRequestResult::INVALID_PROPERTY_VALUE);
        }
        if(!$result->getErrorMask()){
            $token = new static();
            $token->prop = $prop;
            $result->setObject($token);
        }
        return $result;
    }
    ....
}

Я определил интерфейс и абстрактный класс для обработки большей части функциональности объекта Результат запроса .

interface requestResult {
    public function getErrorMask() : int;
    public function getErrorMessages() : array;
    // TODO: When PHP >= 7.4 add typehints as per commented lines below
    public function getObject();
    public function setObject($object);
//  public function getObject() : Object;
//  public function setObject(Object $object);
    public function addError(int $maskBit, ?string $message);
}

abstract class baseRequestResult implements requestResult {
    protected $errorMask = 0;
    protected $errorMessages = [];
    protected $object;

    public function getErrorMask(): int {
        return $this->errorMask;
    }

    public function getErrorMessages(): array {
        return $this->errorMessages;
    }

    public function buildErrorMessageList(dataWriter $writer) : string {
        $response = '';
        if(count($this->errorMessages)){
            $response .= $writer->beforeResults();

            foreach($this->errorMessages as $errorMessage){
                $response .= $writer->item((object)['message'=>$errorMessage]);
            }

            $response .= $writer->afterResults();
        } else {
            $response .= $writer->noResults();
        }
        return $response;
    }

    // TODO: When PHP >= 7.4 add typehints as per interface
    abstract public function getObject();
    abstract public function setObject($object);

    public function addError(int $maskBit, ?string $message) {
        $this->errorMask |= $maskBit;
        if($message && !in_array($message, $this->errorMessages)){
            $this->errorMessages[] = $message;
        }
    }
}

И класс запроса токена действительно очень мал и легко воспроизводим для разных типов объектов:

class tokenRequestResult extends baseRequestResult {
    const INVALID_TYPE = 1<<0;
    const MATCHING_TOKEN_IN_USE = 1<<1;

    // TODO: When PHP >= 7.4 use typehint for \token here instead of instanceof condition
    public function setObject($token) {
        if($token instanceof \token){
            $this->object = $token;
        }
    }

    public function getObject() : \token {
        return $this->object;
    }
}

Внешне API затем используется следующим образом:

$tokenResult = token::request('Foo');
if($tokenResult->getErrorMask()){
    /*
        There are errors that need handling. We can flesh out the 
        tokenRequestResult object to handle the various error mask values 
        and/or error messages, pass in a writer class to output the 
        errors to screen, whatever.
    */

} else {
    /*
        But if reported errors are 0 we can be confident that `->getObject()` 
        will get us our native `token` object and act accordingly
    */
    $token = $tokenResult->getObject();
    $token->callPublicMethodOfTokenObject();
}

Я думаю, что эта техника окажется довольно ценной в моей работе. Как я уже сказал, это, вероятно, задокументированный шаблон, но, учитывая, что я не знаю, как он может называться, я не уверен, как начать его искать.

Если вы случайно знаете его название, я был бы благодарен, но если оно для вас новое Я надеюсь, что вы найдете это полезным.

Оригинал: “https://dev.to/thisleenoble/evolution-of-php-object-instantiation-with-detailed-error-diagnostics-1ddo”