throw new LMBException();

 

 

 

 

Uma visão rápida sobre Exception no PHP e como isso pode influenciar no design do seu código

Agenda

  • Predefined Errors e Exceptions padrão
  • Pokemon
  • Estendendo as exceptions do PHP
  • Fronteira de domínio
  • Relançando Exceptions​
  • Evitando falso positivo
  • Debug
  • Exemplos - bons e ruins
  • Testando
  • Capturando
  • Sugestão API
  • Opiniões

predefined errors e exceptions padrão

predefined errors

predefined errors

Exception

Error

Base para todos os erros de usuário, ou seja, exceptions que lançamos

Base para todos os erros internos do PHP

árvore de exceptions padrão

Exception

Seja

 

 Específico.

 

 

 

pokémon exception



public function gottaCatchThemAll() 
{
    try {
        $this->doASpecificThing();
    } catch (Exception $error) {
        $this->logError();
        throw $error;
    }
}







public function gonnaThrowThemAll() 
{
    if ($this->unexpectedBehavior()) {
        throw new Exception(
            'Sorry, an error occurred. Try again later!'
        );
    }
}
// DynamicRelation.php





public function get($page = 0, $size = 10)
{
    if (empty($this->relativeAttributes)) {
        throw new Exception(
            'Cannot run DynamicRelation before setting a relative set of'
            .'attributes. Run relativeTo() before running get().',
            1
        );
    }

    try {
        return parent::get($page, $size);
    } catch (Exception $e) {
        return;
    }
}

Seja

 

 Específico.

ainda mais

/**
 * Validates the given user data
 *
 * @param UserDTO $userData
 *
 * @throws InvalidArgumentException
 *
 * @return bool
 */
public function validate(UserDTO $userData): bool
{
    if (false === $this->isEmailValid($userData->getEmail()) {
        throw new InvalidArgumentException(
            sprintf(
                'The following e-mail address is invalid: %s', 
                $userData->getEmail()
            )
        );
    }

    return true;
}
/**
 * Stores an user into database
 *
 * @param UserDTO $userData
 *
 * @throws InvalidArgumentException
 *
 * @return User|bool
 */
public function store(UserDTO $userData)
{
    try {
        $this->validate($userData);
    } catch (InvalidArgumentException $error) {
        $this->log->error($error->getMessage());

        return false;
    }

    return new User($userData);
}

Mas e se...



public function validate(UserDTO $userData): bool
{
    if (false === $this->isEmailValid($userData->getEmail()) {
        throw new InvalidArgumentException('...');
    }

    if (false === $this->isIDValid($userData->getID()) {
        throw new InvalidArgumentException('...');
    }

    if (false === $this->isPhoneValid($userData->getPhone()) {
        throw new InvalidArgumentException('...');
    }

    if (false === $this->isMobileValid($userData->getMobile()) {
        throw new InvalidArgumentException('...');
    }

    return true;
}

estenda as exceptions 

/**
 * Exception thrown when you try to validate an invalid user
 *
 */
class UserValidationException() 
    extends InvalidArgumentException
{
    /**
     * Constructor
     *
     * @param string $field 
     * @param string $value
     */ 
    public function __construct($field, $value) {
        parent::__construt(
            sprintf(
                'Invalid value given for %s: %s',
                $field,
                $value
            )
        );
    }
}

/**
 * Exception thrown when you try to validate an invalid user
 *
 */
class UserValidationException() 
    extends InvalidArgumentException
{
    protected $field;
    protected $value;

    public function __construct($field, $value) {
        $this->field = $field;
        $this->value = $value;
        parent::__construct('message...');
    }

    public function getField() { return $this->field; }

    public function getValue() { return $this->vaue; }
}
public function validate(UserDTO $userData): bool
{
    if (false === $this->isEmailValid($userData->getEmail()) {
        throw new UserValidationException(
            'email', 
            $userData->getEmail()
        );
    }

    if (false === $this->isIDValid($userData->getID()) {
        throw new UserValidationException(
            'ID', 
            $userData->getID()
        );
    }

    if (false === $this->isPhoneValid($userData->getPhone()) {
        throw new UserValidationException(
            'phone', 
            $userData->getPhone()
        );
    }

    if (false === $this->isMobileValid($userData->getMobile()) {
        throw new UserValidationException(
            'mobile', 
            $userData->getMobile()
        );
    }

    return true;
}
/**
 * Stores an user into database
 *
 * @param UserDTO $userData
 *
 * @throws InvalidArgumentException
 *
 * @return User|bool
 */
public function store(UserDTO $userData)
{
    try {
        $this->validate($userData);
    } catch (UserValidationException $error) {
        $this->log->error($error->getMessage());
        $this->errorsBag->add(
            $error->getField(), 
            $error->getValue()
        );    

        return false;
    }

    return new User($userData);
}

Seja

 

 Específico.

ainda mais

De verdade mesmo

crie fronteiras

class UserException extends DomainException
{
    protected UserException $previous;

    public function __constructor(UserException $previous)
    {
        parent::__construct(
            $previous->getMessage(),
            $previous->getCode(),
            $previous
        );
    }

    // Retrieves context of specific exception
    abstract public function getContext();

    // Converts exception to array
    public function toArray() 
    {
        return json_encode([
            'message' => $this->getMessage(), 
            'code' => $this->getCode(),
            'context' => $this->getContext(),
        ]);
    }
     
}



public function store(UserDTO $userData)
{
    try {
        $this->validate($userData);
        try {
            $this->checkDebts($userData->getID());
        } catch (UserHasDebtException $error) {
            throw new UserException($error);
        }
    } catch (UserValidationException $error) {
        $this->log->error($error->getMessage());
        $this->errorsBag->add(
            $error->getField(), 
            $error->getValue()
        );    

        throw new UserException($error);
    }

    return new User($userData);
}
// UsersController
public function register(array $input) {
    try {
        $user = $this->user->store(new UserDTO($input));
    } catch (UserException $error) {
        abort(400, $error->toArray());
    }
}

// API
public function register(string $json) {
    try {
        $user = $this->user->store(new UserDTO(json_decode($json, true)));
    } catch (UserException $error) {
        abort(400, $error->toArray());
    }
}

// Job
public function importUsersFromTXT() {
    $errors = [];
    $sucess = [];
    while ($row = $this->file->getNextUser()) {
        try {
            $sucess[] = $this->user->store(new UserDTO($row));
        } catch (UserException $error) {
            $errors[] = $error->toArray();
        }   
    }
    $this->logFinalStatus($sucess, $errors);
}

Evite falso positivo

Se o fluxo não pode seguir normalmente se uma parte do código falhar, então deixe o sistema quebrar

// Kameleon\Picture\Manipulator.php




/**
 * This is the entry point for image cropping. It will call the specific
 * crop methods and merge the path for all the cropped images into a array.
 * This array may be used in order to know all the images that was generated
 * /cropped from the original in order to, for example, send'en to Nas.
 *
 * @param Picture $picture
 *
 * @return array Containing the path of all cropped pictures
 */
public function cropImages(&$picture)
{
    if (!$this->hasValidSizes($picture->sizes)) {
        return false;
    }

    // Crop images
}

Se o fluxo pode seguir normalmente. Capture as exceptions...

mas é vida que segue

// Kameleon\Content\Content.php





/**
 * {@inheritdoc}
 */
public function polymorph()
{
    $factory = app(PolymorphFactory::class);

    try {
        $specialization = $factory->getInstanceFor($this->type);
    } catch (TypeNotFoundException $error) {
        return $this;
    }

    $specialization->fill($this->attributes, true);

    return $specialization;
}

Melhor uma exception gritando no NewRelic onde podemos atuar (quase que) imadiatamente, que um: "Desculpe, estamos em manutenção. Tente novamente mais tarde".

considerações

algumas considerações

  • Facilita o debug

  • Achar erros ainda na parte de codificação
  • Teste suas exceptions se elas tiverem lógica dentro delas

  • Nosso código tende a ser exportado, visto e estendido

sugestão API

respostas de erro hoje



// JobsController
if (!$this->repository->isTriggerableFromAPI($type)) {
    return abort(Response::HTTP_NOT_FOUND, 'Job Type does not exist.');
}

if ($this->repository->hasReachedThreshold($type)) {
    return abort(
        Response::HTTP_TOO_MANY_REQUESTS,
        'Current Job Type has already reached concurrent scheduling threshold'
    );
}

// ProductsController
if (!$storeProduct = $this->repository->first($id)) {
    abort(
        HttpResponse::HTTP_NOT_FOUND,
        'There is no products with given LM.'
    );
}

// UsersController
if (!$user) {
    abort(Response::HTTP_NOT_FOUND, 'There is no user with given key.');
}





/**
 * Exception to throw when an error message should be returned to user
 */
class APIException extends DomainException
{
    // Retrieves context to return in json response
    public function getContext() 
    { 
        return null;     
    }
    
    // Retrieves an array to print in json response
    public function toArray() { 
        return [
            'message' => '', 
            'code' => '', 
            'context' => $this->getContext()
        ]; 
    }
}
// JobNotFoundException
class JobNotFoundException extends APIException
{
    const HTTP_CODE = Response::HTTP_NOT_FOUND;
    const MESSAGE = 'Job Type does not exist.';

    protected $type;

    public function __construct(string $type) 
    {
        $this->type = $type;
        parent::__construct(self::MESSAGE, self::HTTP_CODE);
    }

    public function getContext()
    {
        return ['type' => $this->type];
    }
}

// JobReachedThresoldException
class JobReachedThresoldException extends APIException
{
    const HTTP_CODE = Response::HTTP_TOO_MANY_REQUESTS;
    const MESSAGE = 'Current Job Type has already reached concurrent scheduling threshold';

    protected $type;

    public function __construct(string $type) 
    {
        $this->type = $type;
        parent::__construct(self::MESSAGE, self::HTTP_CODE);
    }

    public function getContext()
    {
        return ['type' => $this->type];
    }
}


// ProductNotFoundException
class ProductNotFoundException extends APIException
{
    const HTTP_CODE = Response::HTTP_NOT_FOUND;
    const MESSAGE = 'There is no products with given LM.';

    protected $lm;

    public function __construct(int $lm) 
    {
        $this->lm = $lm;
        parent::__construct(self::MESSAGE, self::HTTP_CODE);
    }

    public function getContext()
    {
        return ['lm' => $this->lm];
    }
}


// UserNotFoundException
class UserNotFoundException extends APIException
{
    const HTTP_CODE = Response::HTTP_NOT_FOUND;
    const MESSAGE = 'There is no user with given key.';

    protected $uniqueKey;

    public function __construct(string $uniqueKey) 
    {
        $this->uniqueKey = $uniqueKey;
        parent::__construct(self::MESSAGE, self::HTTP_CODE);
    }

    public function getContext()
    {
        return ['uniqueKey' => $this->uniqueKey];
    }
}


// JobsController
if (!$this->repository->isTriggerableFromAPI($type)) {
    return abort(Response::HTTP_NOT_FOUND, 'Job Type does not exist.');
}

if ($this->repository->hasReachedThreshold($type)) {
    return abort(
        Response::HTTP_TOO_MANY_REQUESTS,
        'Current Job Type has already reached concurrent scheduling threshold'
    );
}

// ProductsController
if (!$storeProduct = $this->repository->first($id)) {
    abort(
        HttpResponse::HTTP_NOT_FOUND,
        'There is no products with given LM.'
    );
}

// UsersController
if (!$user) {
    abort(Response::HTTP_NOT_FOUND, 'There is no user with given key.');
}


// JobsController
if (!$this->repository->isTriggerableFromAPI($type)) {
    throw new JobNotFoundException($type);
}

if ($this->repository->hasReachedThreshold($type)) {
    throw new JobReachedTrhesoldException($type);
}

// ProductsController
if (!$storeProduct = $this->repository->first($id)) {
    throw new ProductNotFoundException($id);
}

// UsersController
if (!$user) {
    throw new UserNotFoundException($uniqueKey);
}



/**
 * Catch all API exception
 */
class APIExceptionHandler()
{
    /**
     * Retrieves a standardized response for API
     */
    public function respond($error) 
    {
        return response()->json(
            $error->toArray(), 
            $error->getCode()
        );
    }
}







{
    message: "User not found",
    code: 404, // Ou até pode ser algo mais específico 
               // da API já que isso ele terá no HTTP já,
    context: {
        uniqueKey: "johndoe@test.com"
    }
}

vlw flw

throw new LMBException

By Ricardo Plansky Jr.

throw new LMBException

Uma visão rápida sobre Exception no PHP e como isso pode influenciar no design do seu código

  • 385