wittestier / apic
API Client is a Data Access Object targeting API's.
This package's canonical repository appears to be gone and the package has been frozen as a result.
Requires
- php: ^7.2
- sensiolabs/security-checker: ^5.0
- zendframework/zend-hydrator: ^3
Requires (Dev)
- phpmd/phpmd: ^2.6
- squizlabs/php_codesniffer: ^3.2
Suggests
- guzzlehttp/guzzle: Works well as HTTP client.
This package is auto-updated.
Last update: 2023-12-26 20:59:48 UTC
README
API Client is a Data Access Object targeting API's.
Requirements
The following versions of PHP are supported.
- PHP 7.2
Installation
To install, use composer:
composer require wittestier/apic
Implementation
APIC contains a collection of interfaces, these interfaces guide you to setup a DOA for any web API.
Before you can use APIC, you'll need to create some classes, a working API where you have access to will be a good idea.
Model
Models or entities are Plain Old Php Objects or popo's. A models require one or more properties of any visibility. These models will be populated with the data, fetched from the API.
<?php
declare(strict_types=1);
namespace Api\Model;
class Book
{
private $id;
private $isbn;
private $description;
private $author;
private $title;
private $publicationDate;
}
Api
is the namespace in these examples. You can use any namespace you want. Actually the model is your class.
It can even be the same model as you already use with your ORM.
Gateway
Next, you'll need to create a Gateway
class. A gateway is "an object that encapsulates access to an external system or
resource" - P of EEA. With other words, the gateway is responsible
for the communication with the API server.
For the sake of this example we use Guzzle as HTTP library. You're free to use any HTTP library you like.
Your Gateway
class must implement the Apic\Gateway\GatewayInterface
interface.
<?php
declare(strict_types=1);
namespace Api;
use Apic\Criteria\CriteriaInterface;
use Apic\Gateway\GatewayInterface;
use GuzzleHttp\Client;
class Gateway implements GatewayInterface
{
private $client;
public function __construct(Client $client)
{
$this->client = $client;
}
public function create(array $data, CriteriaInterface $criteria): array
{
return $this->request('POST', $criteria->toString(), $data);
}
public function delete(array $data, CriteriaInterface $criteria): void
{
$data = null;
$this->request('DELETE', $criteria->toString(), $data);
}
public function retrieve(CriteriaInterface $criteria): array
{
return $this->request('GET', $criteria->toString(), null);
}
public function update(array $data, CriteriaInterface $criteria): array
{
return $this->request('PUT', $criteria->toString(), $data);
}
private function request(string $method, string $uri, ?array $payload): array
{
$options = [];
if (empty($payload) === false) {
$options['json'] = \GuzzleHttp\json_encode($payload);
}
$response = $this->client->request($method, $uri, $options);
$result = (string)$response->getBody();
return \GuzzleHttp\json_decode($result, true);
}
}
The create
method is called when a new object is added to a repository, update
is called when an already existing
object is added to a repository.
The delete
method is called when an object is removed from a repository.
And the retrieve
method is called when a repository searches for one or more objects.
Criteria
As you can see, all methods in the gateway accept a Criteria
object. A criteria is "an object that
represents a database query" - P of EAA. In the context of an API,
a criteria object can be used to define query, ordering, sorting, pagination, filtering, searching or any other logic you need.
Your Criteria
class must implement the Apic\Criteria\CriteriaInterface
interface.
<?php
declare(strict_types=1);
namespace Api;
use Apic\Criteria\CriteriaInterface;
class Criteria implements CriteriaInterface
{
private $path;
private $id;
public function __construct(string $path, ?int $id)
{
$this->path = $path;
$this->id = $id;
}
public function toString(): string
{
return \rtrim(join('/', [$this->path, $this->id]), '/');
}
}
Repository
A repository "mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects." - P of EAA.
You'll need to create a repository class for each end-point in the API. You can also say that you need to create a repository for each model. A repository class should implement methods that make sense to your business logic.
Your Repository
class must implement the Apic\Repository\RepositoryInterface
interface or extend the
Apic\Repository\AbstractRepository
class.
<?php
declare(strict_types=1);
namespace Api\Repository;
use Api\Criteria;
use Api\Model\Book;
use Api\Parser\BookInputParser;
use Api\Parser\BookListParser;
use Api\Parser\BookOutputParser;
use Apic\Repository\AbstractRepository;
class BookRepository extends AbstractRepository
{
protected function getIdProperty(): string
{
return 'id';
}
public function findFirstPage(): array
{
return $this->findAll(
Book::class,
new Criteria('/books', null),
new BookListParser()
);
}
public function findById(int $id): ?Book
{
return $this->find(
Book::class,
new Criteria('/books', $id),
new BookInputParser()
);
}
public function persist(Book $book): Book
{
return $this->add(
$book,
new Criteria('/books', $book->getId()),
new BookInputParser(),
new BookOutputParser()
);
}
public function delete(Book $book): void
{
$this->remove(
$book,
new Criteria('/books', $book->getId()),
new BookOutputParser()
);
}
}
A custom repository method can be as simple as creating an Criteria
object and call the underlying add
, find
,
findAll
or remove
methods.
Parser
A parser is an object that parse the API response payload into usable data. Parsers can be used to change the data structure, change value type or anything you need to do with the data before it is populated into models or sent to the API.
<?php
declare(strict_types=1);
namespace Api\Parser;
use Apic\Parser\ParserInterface;
class BookInputParser implements ParserInterface
{
public function parse(array $data): array
{
if (isset($data['publicationDate']) === true) {
$data['publicationDate'] = new \DateTime($data['publicationDate']);
}
return $data;
}
}
<?php
declare(strict_types=1);
namespace Api\Parser;
use Apic\Parser\ParserInterface;
class BookOutputParser implements ParserInterface
{
public function parse(array $data): array
{
if (isset($data['publicationDate']) === true
&& is_a($data['publicationDate'], \DateTime::class) === true
) {
/** @var \DateTime $date */
$date = $data['publicationDate'];
$data['publicationDate'] = $date->format('c');
}
return $data;
}
}
<?php
declare(strict_types=1);
namespace Api\Parser;
use Apic\Parser\ParserInterface;
class BookListParser implements ParserInterface
{
public function parse(array $data): array
{
$bookParser = new BookInputParser();
return array_map(function ($data) use ($bookParser) {
return $bookParser->parse($data);
}, $data['hydra:member']);
}
}
An InputParser
is a parser that parses the payload from the API so it can be used in you application.
An OutputParser
is a parser that parses the data extracted from your models so it can be sent to the API.
Client
<?php
$httpClient = new \GuzzleHttp\Client([
'base_uri' => 'https://demo.api-platform.com',
]);
$gateway = new \Api\Gateway($httpClient);
$client = new \Apic\Client($gateway, new \Zend\Hydrator\Reflection());
<?php
$bookRepository = $client->createRepository(\Api\Repository\BookRepository::class);
// Fetch the first page.
$books = $bookRepository->findFirstPage();
echo '##### Find first 30 books #####' . PHP_EOL;
var_dump($books);
echo '##### end #####' . PHP_EOL;
// Fetch by id
$book = $bookRepository->findById(3);
echo '##### Find book with ID 3 #####' . PHP_EOL;
var_dump($book);
echo '##### end #####' . PHP_EOL;
// Update book
$book = $bookRepository->findById(1);
$book->setAuthor('WitteStier');
$book = $bookRepository->persist($book);
echo '##### Updated book #####' . PHP_EOL;
var_dump($book);
echo '##### end #####' . PHP_EOL;
// Create new book
$book = new \Api\Model\Book();
$book->setIsbn('0132350882');
$book->setAuthor('WitteStier');
$book->setDescription('Test APIC documentation');
$book->setTitle('Test APIC');
$book->setPublicationDate(new \DateTime('now'));
$book = $bookRepository->persist($book);
echo '##### Create book #####' . PHP_EOL;
var_dump($book);
echo '##### end #####' . PHP_EOL;
License
The MIT License (MIT). Please see License File for more information.