Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exploded the base exception into several more specialized exception class #89

Open
wants to merge 12 commits into
base: master
Choose a base branch
from

Conversation

gplanchat
Copy link

Questions Answers
Description? Exploded the base exception into several more specialized exception class, as initially described in https://github.com/PrestaShop/PrestaShop-webservice-lib/issues/77#issuecomment-1520140508
Type? improvement / refactor
BC breaks? no
Deprecations? no
Fixed ticket? Fixes #77
Sponsor company Gyroscops.com
How to test? Any scenario throwing an PrestaShopWebserviceException exception should now throw a more specialized exception implementing PrestaShopWebserviceException (which has now become an interface.

PSWebServiceLibrary.php Outdated Show resolved Hide resolved
PSWebServiceLibrary.php Outdated Show resolved Hide resolved
PSWebServiceLibrary.php Outdated Show resolved Hide resolved
* @package PrestaShopWebservice
*/
class PrestaShopWebserviceBadRequestException extends \RuntimeException implements PrestaShopWebserviceException {
public function __construct($previous = null)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding the Throwable type hints for this parameter everywhere

Suggested change
public function __construct($previous = null)
public function __construct(Throwable $previous = null)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doing so will conflict with the PHP minimum version constraint being 5.3.

Comment on lines +666 to +673
/**
* @param ?\Throwable $previous
* @return self
*/
public static function shouldNotReceive100ContinueStatus($previous = null)
{
return self::shouldNotReceiveStatus(100, 'Continue', $previous);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that this function and the following be overly specific and redundant, forcing the use of different switch/case
Making shouldNotReceiveStatus() public would reduce the amount of repetitive code.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those methods were created to get the status text, as the client does not provide it in the $response array. Not doing so would require to provide a very generic exception message in all those return codes.

All those cases may not be produced by Prestashop's API itself, but they could be produced by the underlying infrastructure and networks between the API and the API client.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah but you don't need one factory method per message, you can handle it this way:

class PrestaShopWebserviceStatusException extends \RuntimeException implements PrestaShopWebserviceException {
	const STATUS_MESSAGES = array(
		100 => 'Continue',
		101 => 'Switching protocols',
		// ...
	);

    /**
     * @param int $code
     * @param ?\Throwable $previous
     * @return self
     */
    public static function shouldNotReceiveStatus($code, $previous = null)
    {
		$status = (isset(self::STATUS_MESSAGES[$code])) ? self::STATUS_MESSAGES[$code] : 'Nonstandard status';
        return new self(
            strtr(
                'This call to PrestaShop Web Services responded with status `%code% %status%`, this client was not designed to process this kind of response. This can come from your web server or reverse proxy configuration.',
                array(
                    '%code%' => $code,
                    '%status%' => $status,
                )
            ),
            $code,
            $previous
        );
    }

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having one factory per message is a practice I use, and personal preference making the code self-descriptive with a minimal cyclomatic complexity.

If you prefer the option with the array, I'll change it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Array in constants are available since PHP 5.6, we would need to use static property instead of constant.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having one factory per message is a practice I use

I think that's generally a good practice when you're throwing exceptions in different scenarios. But in this case, you're just transforming a plethora of error codes into instances of the same exception: you're just transforming an error message from the server into an exception, and only the message changes. When handling a new scenario requires you to change two files to add a new function and a new line in a switch, it's usually a code smell.

Array in constants are available since PHP 5.6

Honestly I don't think anyone who's running PHP < 7 these days will update anything at all on their stack. I wouldn't be against upping the minimum requirements to PHP 7.1.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[...] in this case, you're just transforming a plethora of error codes into instances of the same exception: you're just transforming an error message from the server into an exception, and only the message changes. When handling a new scenario requires you to change two files to add a new function and a new line in a switch, it's usually a code smell.

I'm sorry but I'm not sure what you are asking me to do to avoid code smells.
Do you want me to use an array of error messages with a unique static factory method?
This will not change anything about the switch statement and the number of files to change.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think what Pablo proposed was to use a generic static function able to build the error messages based on the status code This way you don't need a big switch in PsWebServiceLibrary::assertStatusCode the switch is only handled in this exception class And if you use an array to store the messages even the switch part can be avoided

Also I understand the limitation about const in PHP 5.6, what Pablo suggested I think is that maybe we could release a new major version of this library based on PHP 7 to use more modern code As he said people still relying on PHP 5 have a very old environment and are not likely to update their libraries regularly so this wouldn't be a problem

PSWebServiceLibrary.php Outdated Show resolved Hide resolved
PSWebServiceLibrary.php Outdated Show resolved Hide resolved
Comment on lines +722 to +840
}

/**
* @param ?\Throwable $previous
* @return self
*/
public static function shouldNotReceive100ContinueStatus($previous = null)
{
return self::shouldNotReceiveStatus(100, 'Continue', $previous);
}

/**
* @param ?\Throwable $previous
* @return self
*/
public static function shouldNotReceive101SwitchingProtocolsStatus($previous = null)
{
return self::shouldNotReceiveStatus(101, 'Switching Protocols', $previous);
}

/**
* @param ?\Throwable $previous
* @return self
*/
public static function shouldNotReceive102ProcessingStatus($previous = null)
{
return self::shouldNotReceiveStatus(102, 'Processing', $previous);
}

/**
* @param ?\Throwable $previous
* @return self
*/
public static function shouldNotReceive103EarlyHintsStatus($previous = null)
{
return self::shouldNotReceiveStatus(103, 'Early Hints', $previous);
}

/**
* @param ?\Throwable $previous
* @return self
*/
public static function shouldNotReceive300MultipleChoicesStatus($previous = null)
{
return self::shouldNotReceiveStatus(300, 'Multiple Choices', $previous);
}

/**
* @param ?\Throwable $previous
* @return self
*/
public static function shouldNotReceive301MovedPermanentlyStatus($previous = null)
{
return self::shouldNotReceiveStatus(301, 'Moved Permanently', $previous);
}

/**
* @param ?\Throwable $previous
* @return self
*/
public static function shouldNotReceive302FoundStatus($previous = null)
{
return self::shouldNotReceiveStatus(302, 'Found', $previous);
}

/**
* @param ?\Throwable $previous
* @return self
*/
public static function shouldNotReceive303SeeOtherStatus($previous = null)
{
return self::shouldNotReceiveStatus(303, 'See Other', $previous);
}

/**
* @param ?\Throwable $previous
* @return self
*/
public static function shouldNotReceive304NotModifiedStatus($previous = null)
{
return self::shouldNotReceiveStatus(304, 'Not Modified', $previous);
}

/**
* @param ?\Throwable $previous
* @return self
*/
public static function shouldNotReceive307TemporaryRedirectStatus($previous = null)
{
return self::shouldNotReceiveStatus(307, 'Temporary Redirect', $previous);
}

/**
* @param ?\Throwable $previous
* @return self
*/
public static function shouldNotReceive308PermanentRedirectStatus($previous = null)
{
return self::shouldNotReceiveStatus(308, 'Permanent Redirect', $previous);
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/**
* @param int $code
* @param string $status
* @param ?\Throwable $previous
* @return self
*/
private static function shouldNotReceiveStatus($code, $status, $previous = null)
{
return new self(
strtr(
'This call to PrestaShop Web Services responded with status `%code% %status%`, this client was not designed to process this kind of response. This can come from your web server or reverse proxy configuration.',
array(
'%code%' => $code,
'%status%' => $status,
)
),
$code,
$previous
);
}
/**
* @param ?\Throwable $previous
* @return self
*/
public static function shouldNotReceive100ContinueStatus($previous = null)
{
return self::shouldNotReceiveStatus(100, 'Continue', $previous);
}
/**
* @param ?\Throwable $previous
* @return self
*/
public static function shouldNotReceive101SwitchingProtocolsStatus($previous = null)
{
return self::shouldNotReceiveStatus(101, 'Switching Protocols', $previous);
}
/**
* @param ?\Throwable $previous
* @return self
*/
public static function shouldNotReceive102ProcessingStatus($previous = null)
{
return self::shouldNotReceiveStatus(102, 'Processing', $previous);
}
/**
* @param ?\Throwable $previous
* @return self
*/
public static function shouldNotReceive103EarlyHintsStatus($previous = null)
{
return self::shouldNotReceiveStatus(103, 'Early Hints', $previous);
}
/**
* @param ?\Throwable $previous
* @return self
*/
public static function shouldNotReceive300MultipleChoicesStatus($previous = null)
{
return self::shouldNotReceiveStatus(300, 'Multiple Choices', $previous);
}
/**
* @param ?\Throwable $previous
* @return self
*/
public static function shouldNotReceive301MovedPermanentlyStatus($previous = null)
{
return self::shouldNotReceiveStatus(301, 'Moved Permanently', $previous);
}
/**
* @param ?\Throwable $previous
* @return self
*/
public static function shouldNotReceive302FoundStatus($previous = null)
{
return self::shouldNotReceiveStatus(302, 'Found', $previous);
}
/**
* @param ?\Throwable $previous
* @return self
*/
public static function shouldNotReceive303SeeOtherStatus($previous = null)
{
return self::shouldNotReceiveStatus(303, 'See Other', $previous);
}
/**
* @param ?\Throwable $previous
* @return self
*/
public static function shouldNotReceive304NotModifiedStatus($previous = null)
{
return self::shouldNotReceiveStatus(304, 'Not Modified', $previous);
}
/**
* @param ?\Throwable $previous
* @return self
*/
public static function shouldNotReceive307TemporaryRedirectStatus($previous = null)
{
return self::shouldNotReceiveStatus(307, 'Temporary Redirect', $previous);
}
/**
* @param ?\Throwable $previous
* @return self
*/
public static function shouldNotReceive308PermanentRedirectStatus($previous = null)
{
return self::shouldNotReceiveStatus(308, 'Permanent Redirect', $previous);
}
/**
* @param int $code
* @param string $status
* @param ?\Throwable $previous
* @return self
*/
private static function shouldNotReceiveStatus($code, $status, $previous = null)
{
static $messages = array(
100 => 'Continue',
101 => 'Switching protocols',
102 => 'Processing',
103 => 'Early Hints',
300 => 'Multiple Choices',
301 => 'Moved Permanently',
302 => 'Found',
303 => 'See Other',
304 => 'Not Modified',
307 => 'Temporary Redirect',
308 => 'Permanent Redirect'
);
return new self(
strtr(
'This call to PrestaShop Web Services responded with status `%code% %status%`, this client was not designed to process this kind of response. This can come from your web server or reverse proxy configuration.',
array(
'%code%' => $code,
'%status%' => $status,
)
),
$code,
$previous
);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a nice simplification of the code, however $messages is not used in the message output yet And it should probably be a const on the class instead of a static variable You can then get the message via self::STATUS_MESSAGES[$code] thus only one parameter would be needed for this method

Copy link
Author

@gplanchat gplanchat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option with an unique factory method on PrestaShopWebserviceStatusException

Comment on lines +124 to +130
throw PrestaShopWebserviceStatusException::shouldNotReceive100ContinueStatus();
case 101:
throw PrestaShopWebserviceStatusException::shouldNotReceive101SwitchingProtocolsStatus();
case 102:
throw PrestaShopWebserviceStatusException::shouldNotReceive102ProcessingStatus();
case 103:
throw PrestaShopWebserviceStatusException::shouldNotReceive103EarlyHintsStatus();
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
throw PrestaShopWebserviceStatusException::shouldNotReceive100ContinueStatus();
case 101:
throw PrestaShopWebserviceStatusException::shouldNotReceive101SwitchingProtocolsStatus();
case 102:
throw PrestaShopWebserviceStatusException::shouldNotReceive102ProcessingStatus();
case 103:
throw PrestaShopWebserviceStatusException::shouldNotReceive103EarlyHintsStatus();

Comment on lines +132 to +146
throw new PrestaShopWebserviceNoContentException();
case 300:
throw PrestaShopWebserviceStatusException::shouldNotReceive300MultipleChoicesStatus();
case 301:
throw PrestaShopWebserviceStatusException::shouldNotReceive301MovedPermanentlyStatus();
case 302:
throw PrestaShopWebserviceStatusException::shouldNotReceive302FoundStatus();
case 303:
throw PrestaShopWebserviceStatusException::shouldNotReceive303SeeOtherStatus();
case 304:
throw PrestaShopWebserviceStatusException::shouldNotReceive304NotModifiedStatus();
case 307:
throw PrestaShopWebserviceStatusException::shouldNotReceive307TemporaryRedirectStatus();
case 308:
throw PrestaShopWebserviceStatusException::shouldNotReceive308PermanentRedirectStatus();
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
throw new PrestaShopWebserviceNoContentException();
case 300:
throw PrestaShopWebserviceStatusException::shouldNotReceive300MultipleChoicesStatus();
case 301:
throw PrestaShopWebserviceStatusException::shouldNotReceive301MovedPermanentlyStatus();
case 302:
throw PrestaShopWebserviceStatusException::shouldNotReceive302FoundStatus();
case 303:
throw PrestaShopWebserviceStatusException::shouldNotReceive303SeeOtherStatus();
case 304:
throw PrestaShopWebserviceStatusException::shouldNotReceive304NotModifiedStatus();
case 307:
throw PrestaShopWebserviceStatusException::shouldNotReceive307TemporaryRedirectStatus();
case 308:
throw PrestaShopWebserviceStatusException::shouldNotReceive308PermanentRedirectStatus();
case 101:
case 102:
case 103:
case 300:
case 301:
case 302:
case 303:
case 304:
case 307:
case 308:
throw PrestaShopWebserviceStatusException::shouldNotReceiveStatus($statusCode);
case 204:
throw new PrestaShopWebserviceNoContentException();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that having a dedicated method for each status code may be overkill You could either use a generic shouldNotReceiveStatus method as proposed here, or at least a shouldNotReceiveRedirectionStatus for all redirection status maybe?

SAme thing could be done for all the 10* status codes

clemzarch and others added 3 commits May 2, 2023 10:06
display the server's error message to give more context to the API user
…, not the error message (which is a lot less helpful when troubleshooting),

separate multiple error messages with " - "
}

/**
* @param int $statusCode

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @param int $statusCode
* @param array{status_code: int} $request

case 102:
throw PrestaShopWebserviceStatusException::shouldNotReceive102ProcessingStatus();
case 103:
throw PrestaShopWebserviceStatusException::shouldNotReceive103EarlyHintsStatus();
case 204:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is 204 really an error? Usually it's a valid HTTP code, but maybe you throw an exception here because the webservice never use this in theory?

Comment on lines +132 to +146
throw new PrestaShopWebserviceNoContentException();
case 300:
throw PrestaShopWebserviceStatusException::shouldNotReceive300MultipleChoicesStatus();
case 301:
throw PrestaShopWebserviceStatusException::shouldNotReceive301MovedPermanentlyStatus();
case 302:
throw PrestaShopWebserviceStatusException::shouldNotReceive302FoundStatus();
case 303:
throw PrestaShopWebserviceStatusException::shouldNotReceive303SeeOtherStatus();
case 304:
throw PrestaShopWebserviceStatusException::shouldNotReceive304NotModifiedStatus();
case 307:
throw PrestaShopWebserviceStatusException::shouldNotReceive307TemporaryRedirectStatus();
case 308:
throw PrestaShopWebserviceStatusException::shouldNotReceive308PermanentRedirectStatus();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that having a dedicated method for each status code may be overkill You could either use a generic shouldNotReceiveStatus method as proposed here, or at least a shouldNotReceiveRedirectionStatus for all redirection status maybe?

SAme thing could be done for all the 10* status codes

Comment on lines +722 to +840
}

/**
* @param ?\Throwable $previous
* @return self
*/
public static function shouldNotReceive100ContinueStatus($previous = null)
{
return self::shouldNotReceiveStatus(100, 'Continue', $previous);
}

/**
* @param ?\Throwable $previous
* @return self
*/
public static function shouldNotReceive101SwitchingProtocolsStatus($previous = null)
{
return self::shouldNotReceiveStatus(101, 'Switching Protocols', $previous);
}

/**
* @param ?\Throwable $previous
* @return self
*/
public static function shouldNotReceive102ProcessingStatus($previous = null)
{
return self::shouldNotReceiveStatus(102, 'Processing', $previous);
}

/**
* @param ?\Throwable $previous
* @return self
*/
public static function shouldNotReceive103EarlyHintsStatus($previous = null)
{
return self::shouldNotReceiveStatus(103, 'Early Hints', $previous);
}

/**
* @param ?\Throwable $previous
* @return self
*/
public static function shouldNotReceive300MultipleChoicesStatus($previous = null)
{
return self::shouldNotReceiveStatus(300, 'Multiple Choices', $previous);
}

/**
* @param ?\Throwable $previous
* @return self
*/
public static function shouldNotReceive301MovedPermanentlyStatus($previous = null)
{
return self::shouldNotReceiveStatus(301, 'Moved Permanently', $previous);
}

/**
* @param ?\Throwable $previous
* @return self
*/
public static function shouldNotReceive302FoundStatus($previous = null)
{
return self::shouldNotReceiveStatus(302, 'Found', $previous);
}

/**
* @param ?\Throwable $previous
* @return self
*/
public static function shouldNotReceive303SeeOtherStatus($previous = null)
{
return self::shouldNotReceiveStatus(303, 'See Other', $previous);
}

/**
* @param ?\Throwable $previous
* @return self
*/
public static function shouldNotReceive304NotModifiedStatus($previous = null)
{
return self::shouldNotReceiveStatus(304, 'Not Modified', $previous);
}

/**
* @param ?\Throwable $previous
* @return self
*/
public static function shouldNotReceive307TemporaryRedirectStatus($previous = null)
{
return self::shouldNotReceiveStatus(307, 'Temporary Redirect', $previous);
}

/**
* @param ?\Throwable $previous
* @return self
*/
public static function shouldNotReceive308PermanentRedirectStatus($previous = null)
{
return self::shouldNotReceiveStatus(308, 'Permanent Redirect', $previous);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a nice simplification of the code, however $messages is not used in the message output yet And it should probably be a const on the class instead of a static variable You can then get the message via self::STATUS_MESSAGES[$code] thus only one parameter would be needed for this method

Comment on lines +666 to +673
/**
* @param ?\Throwable $previous
* @return self
*/
public static function shouldNotReceive100ContinueStatus($previous = null)
{
return self::shouldNotReceiveStatus(100, 'Continue', $previous);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think what Pablo proposed was to use a generic static function able to build the error messages based on the status code This way you don't need a big switch in PsWebServiceLibrary::assertStatusCode the switch is only handled in this exception class And if you use an array to store the messages even the switch part can be avoided

Also I understand the limitation about const in PHP 5.6, what Pablo suggested I think is that maybe we could release a new major version of this library based on PHP 7 to use more modern code As he said people still relying on PHP 5 have a very old environment and are not likely to update their libraries regularly so this wouldn't be a problem

$resources = $xml->children()->children();

} catch (PrestaShopWebserviceNotFoundException $exception) {
echo 'Bad ID';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a suggestion but maybe this could be implemented by the PrestaShopWebserviceException, maybe not getMessage but an alternative one like getApiErrorMessage this way you could simplify this try/catch

try {
   ...
} catch (PrestaShopWebserviceException $e) {
   echo $e->getApiErrorMessage();
   exit;
}

And leave all the logic about error messages in the exception instead of duplicating it here The base class can implement a generic message base on $this->getMessage while the specific children classes can override the method with a custom message

But it's just a suggestion, not mandatory if you don't think it's relevant

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Ready for review
Development

Successfully merging this pull request may close these issues.

Better Error Handling
5 participants