Last active
January 30, 2021 16:50
-
-
Save marinsagovac/2ad29cb107ebab8b104d612b4dd49875 to your computer and use it in GitHub Desktop.
API Platform Tests
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
API PLATFORM | |
https://api-platform.com | |
https://github.com/api-platform/api-platform | |
### Install ### | |
composer create-project api-platform/api-platform bookshop-api | |
bin/console doctrine:database:create | |
bin/console server:run | |
For existing project install: | |
composer require api-platform/core | |
On current project install schema builder for api platform: | |
composer require api-platform/schema-generator | |
### Get directly path ### | |
http://127.0.0.1:8001/foos | |
Test. | |
### Create a book entity ### | |
Code: | |
# Create a book entity | |
<?php | |
// src/AppBundle/Entity/Book.php | |
namespace AppBundle\Entity; | |
use Doctrine\ORM\Mapping as ORM; | |
/** | |
* A book. | |
* | |
* @ORM\Entity | |
*/ | |
class Book | |
{ | |
/** | |
* @var int The id of this book. | |
* | |
* @ORM\Id | |
* @ORM\GeneratedValue | |
* @ORM\Column(type="integer") | |
*/ | |
private $id; | |
/** | |
* @var string|null The ISBN of this book (or null if doesn't have one). | |
* | |
* @ORM\Column(nullable=true) | |
*/ | |
private $isbn; | |
/** | |
* @var string The title of this book. | |
* | |
* @ORM\Column | |
*/ | |
private $title; | |
/** | |
* @var string The description of this book. | |
* | |
* @ORM\Column(type="text") | |
*/ | |
private $description; | |
/** | |
* @var s# Create a booktring The author of this book. | |
* | |
* @ORM\Column | |
*/ | |
private $author; | |
/** | |
* @var \DateTimeInterface The publication date of this book. | |
* | |
* @ORM\Column(type="datetime") | |
*/ | |
private $publicationDate; | |
/** | |
* @var Review[] Available reviews for this book. | |
* | |
* @ORM\OneToMany(targetEntity="Review", mappedBy="book") | |
*/ | |
private $reviews; | |
} | |
Save to src/AppBundle/Entity/Book.php | |
Create another entity to 'src/AppBundle/Entity/Review.php': | |
<?php | |
// src/AppBundle/Entity/Review.php | |
namespace AppBundle\Entity; | |
use Doctrine\ORM\Mapping as ORM; | |
/** | |
* A review of a book. | |
* | |
* @ORM\Entity | |
*/ | |
class Review | |
{ | |
/** | |
* @var int The id of this review. | |
* | |
* @ORM\Id | |
* @ORM\GeneratedValue | |
* @ORM\Column(type="integer") | |
*/ | |
private $id; | |
/** | |
* @var int The rating of this review (between 0 and 5). | |
* | |
* @ORM\Column(type="smallint") | |
*/ | |
private $rating; | |
/** | |
* @var string the body of the review. | |
* | |
* @ORM\Column(type="text") | |
*/ | |
private $body; | |
/** | |
* @var string The author of the review. | |
* | |
* @ORM\Column | |
*/ | |
private $author; | |
/** | |
* @var \DateTimeInterface The date of publication of this review. | |
* | |
* @ORM\Column(type="datetime") | |
*/ | |
private $publicationDate; | |
/** | |
* @var Book The book this review is about. | |
* | |
* @ORM\ManyToOne(targetEntity="Book", inversedBy="reviews") | |
*/ | |
private $book; | |
} | |
So for implementation API platform you need anotations for ApiResource: | |
### Example: ### | |
<?php | |
// src/AppBundle/Entity/Book.php | |
namespace AppBundle\Entity; | |
use Doctrine\ORM\Mapping as ORM; | |
use ApiPlatform\Core\Annotation\ApiResource; # ADD THIS | |
/** | |
* A book. | |
* | |
* @ApiResource # ADD THIS | |
* @ORM\Entity | |
*/ | |
class Book | |
### Finish test ### | |
Generate schema to database: | |
doctrine:schema:update --force | |
Or of existing check schema: | |
bin/console doctrine:schema:update --force | |
Check in db tables: book, foo, review | |
Reload site. API reloaded. | |
### Generate entity ### | |
Generate entity and than add own methods and references of ApiResource: | |
bin/console doctrine:generate:entities AppBundle | |
bin/console doctrine:schema:update --force | |
Then generate entity setters and getters: | |
bin/console doctrine:generate:entities AppBundle | |
Then: | |
bin/console doctrine:schema:update --force | |
### Generate entity ### | |
Turn of Varnish: | |
On 'app/config/config.yml' set invalidation to false. | |
api_platform: | |
http_cache: | |
invalidation: | |
enabled: false | |
Clear a cache: | |
bin/console cache:clear | |
Run POST request and now works. Test it under http://127.0.0.1:8001/app_dev.php to see Symfony debug. | |
### Check configurations ### | |
https://api-platform.com/docs/core/configuration | |
### Testing API ### | |
# Insert book | |
{ | |
"isbn": "9781782164104", | |
"title": "Persistence in PHP with the Doctrine ORM", | |
"description": "This book is designed for PHP developers and architects who want to modernize their skills through better understanding of Persistence and ORM.", | |
"author": "Kévin Dunglas", | |
"publicationDate": "2013-12-01" | |
} | |
# Get book | |
curl -X GET "http://127.0.0.1:8000/books" -H "accept: application/ld+json" | |
# Retrieve book by ID: 1 | |
curl -X GET "http://127.0.0.1:8000/books/1" -H "accept: application/ld+json" | |
### OTHER REFERENCES | |
# Only GET method per class | |
* @ApiResource( | |
* collectionOperations={"get"={"method"="GET"}}, | |
* itemOperations={"get"={"method"="GET"}} | |
* ) | |
# To set up collection of subresource set '@ApiSubresource' in annotations: | |
<?php | |
use ApiPlatform\Core\Annotation\ApiProperty; | |
use ApiPlatform\Core\Annotation\ApiResource; | |
use ApiPlatform\Core\Annotation\ApiSubresource; | |
use Doctrine\ORM\Mapping as ORM; | |
/** | |
* @ORM\Entity | |
* @ApiResource | |
*/ | |
class Question | |
{ | |
/** | |
* @ORM\Column(type="integer") | |
* @ORM\Id | |
* @ORM\GeneratedValue(strategy="AUTO") | |
*/ | |
private $id; | |
/** | |
* @ORM\Column | |
*/ | |
public $content; | |
/** | |
* @ORM\OneToOne(targetEntity="Answer", inversedBy="question") | |
* @ORM\JoinColumn(referencedColumnName="id", unique=true) | |
* @ApiSubresource | |
*/ | |
public $answer; | |
public function getId() | |
{ | |
return $this->id; | |
} | |
} | |
# Override order of collection, change in class entity annotation: | |
/** | |
* @ApiResource(attributes={"order"={"foo": "ASC"}}) | |
*/ | |
# Override order of collection with multiple fields, change in class entity annotation: | |
/** | |
* @ApiResource(attributes={"order"={"foo", "bar"}}) | |
*/ | |
# Filter by specific collection and ordering by author collection with username field | |
/** | |
* @ApiResource(attributes={"order"={"author.username"}}) | |
*/ | |
# Filter by specified term | |
# app/config/api_filters.yml | |
services: | |
offer.search_filter: | |
parent: 'api_platform.doctrine.orm.search_filter' | |
arguments: [ { id: 'exact', price: 'exact', name: 'partial' } ] | |
tags: [ 'api_platform.filter' ] | |
<?php | |
// src/AppBundle/Entity/Offer.php | |
namespace AppBundle\Entity; | |
use ApiPlatform\Core\Annotation\ApiResource; | |
/** | |
* @ApiResource(attributes={"filters"={"offer.search_filter"}}) | |
*/ | |
class Offer | |
{ | |
// ... | |
} | |
### Using SCHEMA.ORG REFERENCE TO AUTOGENERATE | |
Create schema.yml in app/config: | |
# The list of types and properties we want to use | |
types: | |
# Parent class of Person | |
Thing: | |
properties: | |
name: ~ | |
Person: | |
properties: | |
familyName: ~ | |
givenName: ~ | |
additionalName: ~ | |
gender: ~ | |
address: ~ | |
birthDate: ~ | |
telephone: ~ | |
email: ~ | |
url: ~ | |
jobTitle: ~ | |
PostalAddress: | |
# Disable the generation of the class hierarchy for this type | |
parent: false | |
properties: | |
# Force the type of the addressCountry property to text | |
addressCountry: { range: "Text" } | |
addressLocality: ~ | |
addressRegion: ~ | |
postOfficeBoxNumber: ~ | |
postalCode: ~ | |
streetAddress: ~ | |
Generate from schema entity: | |
vendor/bin/schema generate-types src/ app/config/schema.yml | |
bin/console doctrine:generate:entities AppBundle | |
Update db: | |
bin/console doctrine:schema:update --force | |
Reload API reference from endpoint. | |
# Filters | |
Insert: | |
use ApiPlatform\Core\Annotation\ApiResource; | |
## Date Filter | |
/** | |
* @ApiResource(attributes={"filters"={"offer.date_filter"}}) | |
*/ | |
class Offer | |
{ | |
// ... | |
} | |
## Boolean | |
/** | |
* @ApiResource(attributes={"filters"={"offer.boolean_filter"}}) | |
*/ | |
class Offer | |
{ | |
// ... | |
} | |
To get: | |
?property=[true|false|1|0] | |
## Numeric filter | |
@ApiResource(attributes={"filters"={"offer.numeric_filter"}}) | |
Query: | |
/offers?sold=1 | |
## Range filter | |
@ApiResource(attributes={"filters"={"offer.range_filter"}}) | |
/offers?price[between]=12.99..15.99 | |
## Order | |
@ApiResource(attributes={"filters"={"offer.order_filter"}}) | |
/offers?order[name]=desc&order[id]=asc | |
# SERIALIZATION | |
<?php | |
// src/AppBundle/Entity/Book.php | |
namespace AppBundle\Entity; | |
use ApiPlatform\Core\Annotation\ApiResource; | |
use Symfony\Component\Serializer\Annotation\Groups; | |
/** | |
* @ApiResource( | |
* attributes={ | |
* "normalization_context"={"groups"={"get"}} | |
* }, | |
* itemOperations={ | |
* "get"={"method"="GET"}, | |
* "put"={"method"="PUT", "normalization_context"={"groups"={"put"}}} | |
* } | |
* ) | |
*/ | |
class Book | |
{ | |
/** | |
* @Groups({"get", "put"}) | |
*/ | |
private $name; | |
/** | |
* @Groups("get") | |
*/ | |
private $author; | |
// ... | |
} | |
## Normaliyation | |
<?php | |
// src/AppBundle/Entity/Book.php | |
namespace AppBundle\Entity; | |
use ApiPlatform\Core\Annotation\ApiResource; | |
use Symfony\Component\Serializer\Annotation\Groups; | |
/** | |
* @ApiResource(attributes={ | |
* "normalization_context"={"groups"={"book"}} | |
* }) | |
*/ | |
class Book | |
{ | |
/** | |
* @Groups({"book"}) | |
*/ | |
private $name; | |
/** | |
* @Groups({"book"}) | |
*/ | |
private $author; | |
// ... | |
} | |
<?php | |
// src/AppBundle/Entity/Person.php | |
namespace AppBundle\Entity; | |
use ApiPlatform\Core\Annotation\ApiResource; | |
use Symfony\Component\Serializer\Annotation\Groups; | |
/** | |
* @ApiResource | |
*/ | |
class Person | |
{ | |
/** | |
* ... | |
* @Groups("book") | |
*/ | |
public $name; | |
// ... | |
} | |
The generated JSON with previous settings will be like the following: | |
{ | |
"@context": "/contexts/Book", | |
"@id": "/books/62", | |
"@type": "Book", | |
"name": "My awesome book", | |
"author": { | |
"@id": "/people/59", | |
"@type": "Person", | |
"name": "Kévin Dunglas" | |
} | |
} | |
## Denromaliyation | |
<?php | |
// src/AppBundle/Entity/Book.php | |
namespace AppBundle\Entity; | |
use ApiPlatform\Core\Annotation\ApiResource; | |
/** | |
* @ApiResource(attributes={ | |
* "denormalization_context"={"groups"={"book"}} | |
* }) | |
*/ | |
class Book | |
{ | |
// ... | |
} | |
## Make CSV content negotiation | |
<?php | |
// src/AppBundle/Serializer/CustomItemNormalizer.php | |
namespace AppBundle\Serializer; | |
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; | |
use Symfony\Component\Serializer\Normalizer\NormalizerInterface; | |
class CustomItemNormalizer implements NormalizerInterface, DenormalizerInterface | |
{ | |
// ... | |
public function normalize($object, $format = null, array $context = []) | |
{ | |
$result = $this->normalizer->normalize($object, $format, $context); | |
if ('csv' !== $format || !is_array($result)) { | |
return $result; | |
} | |
foreach ($result as $key => $value) { | |
if (is_array($value) && array_keys(array_keys($value)) === array_keys($value)) { | |
unset($result[$key]); | |
} | |
} | |
return $result; | |
} | |
// ... | |
} | |
formats: | |
jsonld: ['application/ld+json'] | |
jsonhal: ['application/hal+json'] | |
json: ['application/json'] | |
xml: ['application/xml', 'text/xml'] | |
html: ['text/html'] | |
myformat: ['application/vnd.myformat'] | |
# VALIDATION | |
<?php | |
// src/AppBundle/Entity/Book.php | |
use ApiPlatform\Core\Annotation\ApiResource; | |
use Symfony\Component\Validator\Constraints as Assert; | |
/** | |
* @ApiResource(attributes={"validation_groups"={"a", "b"}}) | |
*/ | |
class Book | |
{ | |
/** | |
* @Assert\NotBlank(groups={"a"}) | |
*/ | |
private $name; | |
/** | |
* @Assert\NotNull(groups={"b"}) | |
*/ | |
private $author; | |
// ... | |
} | |
When sending request without group it returns: | |
ERROR 400: | |
{ | |
"@context": "/contexts/ConstraintViolationList", | |
"@type": "ConstraintViolationList", | |
"hydra:title": "An error occurred", | |
"hydra:description": "isbn: This value should not be blank.\ntitle: This value should not be blank.", | |
"violations": [ | |
{ | |
"propertyPath": "isbn", | |
"message": "This value should not be blank." | |
}, | |
{ | |
"propertyPath": "title", | |
"message": "This value should not be blank." | |
} | |
] | |
} | |
# PAGINATION | |
<?php | |
// src/AppBundle/Entity/Book.php | |
use ApiPlatform\Core\Annotation\ApiResource; | |
/** | |
* @ApiResource(attributes={"pagination_enabled"=false}) | |
*/ | |
class Book | |
{ | |
// ... | |
} | |
or globally: | |
# app/config/config.yml | |
api_platform: | |
collection: | |
pagination: | |
client_enabled: true | |
enabled_parameter_name: pagination # optional | |
Number: | |
* @ApiResource(attributes={"pagination_items_per_page"=30}) | |
or: | |
# app/config/config.yml | |
api_platform: | |
collection: | |
pagination: | |
items_per_page: 30 # Default value | |
Client-side: | |
# app/config/config.yml | |
api_platform: | |
collection: | |
pagination: | |
client_items_per_page: true # Disabled by default | |
items_per_page_parameter_name: itemsPerPage # Default value | |
<?php | |
// src/AppBundle/Entity/Book.php | |
use ApiPlatform\Core\Annotation\ApiResource; | |
/** | |
* @ApiResource(attributes={"pagination_client_items_per_page"=true}) | |
*/ | |
class Book | |
{ | |
// ... | |
} | |
# EVENT SYSTEM | |
https://api-platform.com/docs/core/events | |
# DATA PROVIDERS | |
Blocking anonymouse users: | |
# app/config/security.yml | |
security: | |
# ... | |
access_control: | |
# ... | |
- { path: ^/offers, roles: IS_AUTHENTICATED_FULLY } | |
- { path: ^/users, roles: IS_AUTHENTICATED_FULLY } | |
# REFERENCES | |
Swagger support: | |
https://api-platform.com/docs/core/swagger | |
Example JSON+LD | |
GET http://json-ld.org/contexts/person.jsonld | |
Eager loadingL | |
/** | |
* @ApiProperty(attributes={"fetchEager": true}) | |
*/ | |
public $foo; | |
# RESTRICTIONS | |
/ 30 joins per query | |
Throws: ApiPlatform\Core\Exception\RuntimeException | |
# app/config/config.yaml | |
api_platform: | |
eager_loading: | |
max_joins: 100 | |
# FORCE EAGER LOADING | |
# app/config/config.yaml | |
api_platform: | |
eager_loading: | |
force_eager: false | |
<?php | |
// src/AppBundle/Entity/Address.php | |
namespace AppBundle\Entity; | |
use ApiPlatform\Core\Annotation\ApiResource; | |
use Doctrine\ORM\Mapping as ORM; | |
/** | |
* @ApiResource | |
* @ORM\Entity | |
*/ | |
class Address | |
{ | |
// ... | |
} | |
<?php | |
// src/AppBundle/Entity/User.php | |
namespace AppBundle\Entity; | |
use ApiPlatform\Core\Annotation\ApiResource; | |
use Doctrine\ORM\Mapping as ORM; | |
/** | |
* @ApiResource(attributes={"force_eager"=false}) | |
* @ORM\Entity | |
*/ | |
class User | |
{ | |
/** | |
* @var Address | |
* | |
* @ORM\ManyToOne(targetEntity="Address", fetch="EAGER") | |
*/ | |
public $address; | |
/** | |
* @var Group[] | |
* | |
* @ORM\ManyToMany(targetEntity="Group", inversedBy="users") | |
* @ORM\JoinTable(name="users_groups") | |
*/ | |
public $groups; | |
} | |
<?php | |
// src/AppBundle/Entity/Group.php | |
namespace AppBundle\Entity; | |
use ApiPlatform\Core\Annotation\ApiResource; | |
use Doctrine\ORM\Mapping as ORM; | |
/** | |
* @ApiResource( | |
* attributes={"force_eager"=false}, | |
* itemOperations={ | |
* "get"={"method"="GET", "force_eager"=true}, | |
* "post"={"method"="POST"} | |
* }, | |
* collectionOperations={ | |
* "get"={"method"="GET", "force_eager"=true}, | |
* "post"={"method"="POST"} | |
* } | |
* ) | |
* @ORM\Entity | |
*/ | |
class Group | |
{ | |
/** | |
* @var User[] | |
* | |
* @ManyToMany(targetEntity="User", mappedBy="groups") | |
*/ | |
public $users; | |
} | |
# PATH | |
# app/config/config.yml | |
# app/config/config.yml | |
api_platform: | |
path_segment_name_generator: api_platform.path_segment_name_generator.dash | |
Service name: | |
api_platform.path_segment_name_generator.dash | |
Entity name: | |
MyResource | |
Path: | |
/my-resources | |
for: | |
api_platform.path_segment_name_generator.underscore | |
will be: | |
/my_resources | |
# FOSUserBundle Integration | |
https://symfony.com/doc/master/bundles/FOSUserBundle/index.html | |
https://api-platform.com/docs/core/fosuser-bundle | |
# NELMIO API Generator | |
https://api-platform.com/docs/core/nelmio-api-doc | |
Setup: | |
# app/config/config.yml | |
api_platform: | |
# ... | |
enable_nelmio_api_doc: true | |
nelmio_api_doc: | |
sandbox: | |
accept_type: 'application/json' | |
body_format: | |
formats: ['json'] | |
default_format: 'json' | |
request_format: | |
formats: | |
json: 'application/json' | |
# DEPLOYMENT | |
Heroku, Docker: | |
https://api-platform.com/docs/deployment | |
# OVERRIDE LISTENER IN API PLATFORM | |
Copy from vendor EventListener/YourListener.php to your project src/EventListener/<listenerfile.php> | |
Register to service like: | |
application_frontend.listener.player: | |
class: AppBundle\EventListener\RespondListener | |
tags: | |
- { name: kernel.event_listener, event: kernel.view, method: onKernelView, priority: 200 } | |
Make sure that instance in listener properly used event: kernel.view for 'use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;' service: | |
Change priority to 200 or heigher level to register. Check level with: | |
php bin/console debug:event-dispatcher kernel.view | |
==== USEFUL TIPS ==== | |
== CUSTOM ENDPOINTS == | |
To create custom route, example: "/register" inside User class, just add in entity User on annotations: | |
/** | |
* User | |
* | |
* @ApiResource(itemOperations={ | |
* "put"={"method"="PUT", "path"="/register", "hydra_context"={"foo"="bar"}} | |
* }) | |
* @ApiResource() | |
* @ORM\Entity | |
* @ORM\Table(name="user") | |
*/ | |
class User implements UserInterface | |
== ... == | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment