PHP code example of simple-as-fuck / php-api-toolkit
1. Go to this page and download the library: Download simple-as-fuck/php-api-toolkit library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
simple-as-fuck / php-api-toolkit example snippets
'some_api_name' => [ // this key is value of first parameter ApiClient::request method
'base_url' => 'https://some-host/some-base-url', // ll, // https://swagger.io/docs/specification/authentication/bearer-authentication/
],
'deprecated_header' => 'Deprecated', // optional default 'Deprecated', define name of deprecated response header logged into deprecation log
],
/**
* @var \SimpleAsFuck\ApiToolkit\Service\Client\Config $config
* @var \Psr\Log\LoggerInterface $logger
*/
/** @var \SimpleAsFuck\ApiToolkit\Service\Client\DeprecationsLogger|null $deprecationsLogger */
$deprecationsLogger = new \SimpleAsFuck\ApiToolkit\Service\Client\DeprecationsLogger(
$config,
$logger,
);
$client = new \SimpleAsFuck\ApiToolkit\Service\Client\ApiClient(
$config,
new \GuzzleHttp\Client(),
new \GuzzleHttp\Psr7\HttpFactory(),
$deprecationsLogger
);
/**
* @var RequestDataClass $dataForRequestBody
* @var \SimpleAsFuck\ApiToolkit\Service\Transformation\Transformer<RequestDataClass> $transformerForRequestBody
* @var \SimpleAsFuck\Validator\Rule\Custom\UserClassRule<ResponseDataClass> $classRuleForResponseBody
*/
try {
$responseObject = $client->requestObject(
'some_api_name',
'POST',
'/to-some-action',
$dataForRequestBody,
$transformerForRequestBody,
options: [\GuzzleHttp\RequestOptions::TIMEOUT => 3600],
);
/*
* response has getter for json decoded body which is validated after decoding by rule chain
* request method return object rule, so you can easily validate response json structure
* is recommended use some you class rule documented here: https://github.com/simple-as-fuck/php-validator#user-class-rule
* and convert api data structure into some your specific object instance
*/
$dataFromResponseBody = $responseObject->class($classRuleForResponseBody)->notNull();
}
catch (\SimpleAsFuck\ApiToolkit\Data\Client\ApiException $exception) {
/*
* if anything go wrong in request/response processing or response json parsing
* \SimpleAsFuck\ApiToolkit\Model\Client\ApiException is thrown,
* and you can handle any error from communication
*/
// if exception contains http response, rfc9457 status or http status is here, otherwise zero is returned
$exception->getCode();
// exception message for logging or debugging is build from https://datatracker.ietf.org/doc/html/rfc9457
// extended with optional message property, you SHOULD log this, so you know WTF is going wrong
$logger->error($exception->getMessage());
// short information for end user WTF just happened, if is not null you SHOULD show the tittle on your front end
$exception->getProblemDetail()?->title;
// information for end user with more detail, if is not null you SHOULD show the detail on your front end,
// because detail can contain clue or information how user can solve error, mainly if error is his false :D
$exception->getProblemDetail()?->detail;
// parse from error response some extensions, is RECOMMENDED ignoring all errors from error response parsing
// because you can lose another useful data from error response or if response si corrupted you can lose previous exception
$exception->getProblemDetailExtensions()?->property('some_error_property')->string()->nullable(failAsNull: true);
}
/**
* @var \SimpleAsFuck\ApiToolkit\Service\Client\ApiClient $client
*/
// method call POST /webhook request
$webhook = $client->addWebhookListener('some_api_name', 'some_webhook_event_type', 'https://some-client/listening-url');
// you can register listener URL with priority and first,
// if on server is more than one listener for same webhook type,
// this behaviours is implemented in server services in this package,
// but other server implementations can behave differently
// or webhook functionality may not be implemented, so always read specific API documentation!
$webhook = $client->addWebhookListener(
'some_api_name',
'some_webhook_event_type',
'https://some-client/listening-url',
\SimpleAsFuck\ApiToolkit\Model\Webhook\Priority::NORMAL,
['some_key' => '89']
);
// you can save webhook identifier for future use
// deletion while listening is no longer needed, or some data loading in listening url
$webhook->id;
class YourListeningController
{
public function handle(
\Psr\Http\Message\ServerRequestInterface $request
//\Symfony\Component\HttpFoundation\Request $request
): \Psr\Http\Message\ResponseInterface {
//): \Symfony\Component\HttpFoundation\Response {
$rules = \SimpleAsFuck\ApiToolkit\Factory\Server\Validator::make($request)->webhook();
//$rules = \SimpleAsFuck\ApiToolkit\Factory\Symfony\Validator::make($request)->webhook();
$webhook = $rules->notNull();
$attribute = $rules->attributes()->key('some_attribute')->parseInt()->positive()->nullable();
$attribute = $webhook->params->attributes['some_attribute'] ?? null;
// run some you logic
// you should expect than listening action can be called multiple times
// because of some network error or another failure
$response = \SimpleAsFuck\ApiToolkit\Factory\Server\ResponseFactory::makeWebhookResult();
//$response = \SimpleAsFuck\ApiToolkit\Factory\Symfony\ResponseFactory::makeWebhookResult();
$response = \SimpleAsFuck\ApiToolkit\Factory\Server\ResponseFactory::makeWebhookResult(
// you can inform server site application to stop
// dispatching webhook for another listener after current listener
// which has less priority
// server services in this package support this functionality
stopDispatching: true
);
return $response;
}
}
// star of your action
$rules = \SimpleAsFuck\ApiToolkit\Factory\Server\Validator::make($request);
//$rules = \SimpleAsFuck\ApiToolkit\Factory\Symfony\Validator::make($request);
// validate some query parameter
$someQueryValidValue = $rules->query()->key('someKey')->string()->parseInt()->positive()->notNull();
/** @var \SimpleAsFuck\ApiToolkit\Service\Server\UserQueryRule<YourClass> $yourQueryRule */
$yourObjectFromRequestQuery = $rules->query()->class($yourQueryRule)->notNull();
// validate something from request body with json format
$someJsonValidValue = $rules->json()->object()->property('someProperty')->string()->notEmpty()->maxChar(255)->notNull();
/** @var \SimpleAsFuck\Validator\Rule\Custom\UserClassRule<YourClass> $yourClassRule */
$yourObjectFromRequestBody = $rules->json()->object()->class($yourClassRule)->notNull();
// http error in your action
/** @var bool $shitHappens */
if ($shitHappens) {
throw new \SimpleAsFuck\ApiToolkit\Model\Server\ApiException(
'Shit Happens',
new \SimpleAsFuck\ApiToolkit\DataObject\Common\ProblemDetail(
'https://shit-happens.wtf/error',
418,
'Shit happens',
'Developers are looking for some shit in their code.',
'/error/418',
),
(object) ['wtf' => 418],
internalMessage: 'Shit Happens, enjoy looking for what happens in the code.'
);
//throw new \Symfony\Component\HttpKernel\Exception\HttpException(418, 'Shit Happens'),
}
// end of your action
/**
* @var YourClass $yourDataForResponseBody
* @var \SimpleAsFuck\ApiToolkit\Service\Transformation\Transformer<YourClass> $transformer
*/
// response with one object
$response = \SimpleAsFuck\ApiToolkit\Factory\Server\ResponseFactory::makeJson($yourDataForResponseBody, $transformer, \Kayex\HttpCodes::HTTP_OK);
//$response = \SimpleAsFuck\ApiToolkit\Factory\Symfony\ResponseFactory::makeJson($yourDataForResponseBody, $transformer, \Kayex\HttpCodes::HTTP_OK);
// response with some array or collection (avoiding out of memory problem recommended some lazy loading iterator)
$response = \SimpleAsFuck\ApiToolkit\Factory\Server\ResponseFactory::makeJsonStream(new \ArrayIterator([$yourDataForResponseBody]), $transformer);
//$response = \SimpleAsFuck\ApiToolkit\Factory\Symfony\ResponseFactory::makeJsonStream(new \ArrayIterator([$yourDataForResponseBody]), $transformer);
//$response = \SimpleAsFuck\ApiToolkit\Factory\Symfony\ResponseFactory::makeJsonStream([$yourDataForResponseBody], $transformer);
/**
* @var \SimpleAsFuck\ApiToolkit\Service\Config\Repository $configRepository
* @var \Psr\Log\LoggerInterface $logger
*/
try {
// some breakable logic
}
catch(\SimpleAsFuck\ApiToolkit\Model\Server\ApiException $exception) {
// exception message for logging or debugging, you SHOULD log this, so you know WTF is going wrong
$logger->error(implode(', ', [$exception->getMessage(), (string) $exception->getInternalMessage()]), [
'type' => $exception->getProblemDetail()?->type,
'status' => $exception->getCode(),
'instance' => $exception->getProblemDetail()?->instance,
'extensions' => $exception->getProblemDetailExtensions(),
]);
$response = \SimpleAsFuck\ApiToolkit\Factory\Server\ResponseFactory::makeJson(
//$response = \SimpleAsFuck\ApiToolkit\Factory\Symfony\ResponseFactory::makeJson(
$exception,
// transformer will convert exception in to https://datatracker.ietf.org/doc/html/rfc9457 json object with message and all another extensions
new \SimpleAsFuck\ApiToolkit\Service\Server\ApiExceptionTransformer(),
$exception->getCode()
);
}
// if you use Symfony Http Exceptions you can use HttpExceptionTransformer
catch (\Symfony\Component\HttpKernel\Exception\HttpExceptionInterface $exception) {
$logger->error($exception->getMessage(), ['status' => $exception->getStatusCode()]);
$response = \SimpleAsFuck\ApiToolkit\Factory\Symfony\ResponseFactory::makeJson(
$exception,
// transformer will convert exception into json object
// with message property contains message from http exception
// and https://datatracker.ietf.org/doc/html/rfc9457#name-status property
new \SimpleAsFuck\ApiToolkit\Service\Symfony\HttpExceptionTransformer(),
$exception->getStatusCode()
);
}
catch (\Throwable $exception) {
$logger->error($exception->getMessage());
$response = \SimpleAsFuck\ApiToolkit\Factory\Server\ResponseFactory::makeJson(
//$response = \SimpleAsFuck\ApiToolkit\Factory\Symfony\ResponseFactory::makeJson(
$exception,
// transformer will convert exception in to json object
// if application has turned off debug, message property contain only "Internal server error"
// but with enabled debug message contains exception type, message, file and line where was exception thrown
// with enabled debug json object also contains trace property with exception stacktrace
// and json object contains https://datatracker.ietf.org/doc/html/rfc9457#name-status property always with 500 http code
new \SimpleAsFuck\ApiToolkit\Service\Server\ExceptionTransformer($configRepository),
\Kayex\HttpCodes::HTTP_INTERNAL_SERVER_ERROR
);
}
/**
* @var \SimpleAsFuck\ApiToolkit\Service\Webhook\Repository $webhookRepository
* @var \SimpleAsFuck\ApiToolkit\Service\Webhook\WebhookClient $webhookClient
*/
$dispatcher = new \SimpleAsFuck\ApiToolkit\Service\Webhook\WebhookDispatcher($webhookRepository, $webhookClient);
// simplest dispatch, when something happened on the server side,
// webhooks calls are added into a queue
$dispatcher->dispatch('some_webhook_event_type', options: [\GuzzleHttp\RequestOptions::TIMEOUT => 20]);
// webhook call with some attribute,
// for example, you can dispatch an event type with some specific entity id
$dispatcher->dispatch('some_webhook_event_type', attributes: ['some_attribute' => '1256'], options: [\GuzzleHttp\RequestOptions::TIMEOUT => 20]);
// webhook call try immediately without adding call into queue
// only if the first call fails, webhook call is added into queue for retry
$dispatcher->call('some_webhook_event_type', options: [\GuzzleHttp\RequestOptions::TIMEOUT => 20]);
// simple dispatch when the first call will try after 1 minute
$dispatcher->dispatchWithDelay(60, 'some_webhook_event_type', options: [\GuzzleHttp\RequestOptions::TIMEOUT => 20]);
// you can build webhook sequence by your custom logic without using $webhookRepository
// beware you still need some queue for webhook retries if calls failed
/** @var iterable<\SimpleAsFuck\ApiToolkit\Model\Webhook\Webhook> $webhooks */
$webhookClient->dispatchWebhooks($webhooks, options: [\GuzzleHttp\RequestOptions::TIMEOUT => 20]);
// first try without queue
$webhookClient->callWebhooks($webhooks, options: [\GuzzleHttp\RequestOptions::TIMEOUT => 20]);