Skip to content

Instantly share code, notes, and snippets.

@marinsagovac
Last active January 30, 2021 16:50
Show Gist options
  • Save marinsagovac/2ad29cb107ebab8b104d612b4dd49875 to your computer and use it in GitHub Desktop.
Save marinsagovac/2ad29cb107ebab8b104d612b4dd49875 to your computer and use it in GitHub Desktop.
API Platform Tests
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