We are eCommerce development experts who love Open Source and knowledge sharing.
Lionframe is a tool to develop powerful REST APIs with ease and best practices.
It is based on Symfony Standard Edition and uses several community libraries, including SyliusResourceBundle, FOSRestBundle and BazingaHateoasBundle.
Composer is a dependency management library for PHP, which you can use to download the Symfony Standard Edition.
Start by downloading Composer anywhere onto your local computer. If you have curl installed, it is as easy as:
curl -s https://getcomposer.org/installer | php
Composer is an executable PHAR file, which you can use to download Lionframe:
php composer.phar create-project lakion/lionframe path/to/project '0.2.0' --prefer-dist
After Composer is done with installing all the dependencies, create database and run the built in server:
php app/console doctrine:database:create
php app/console doctrine:schema:create
php app/console server:run
Lionframe contains a simple “AcmeDemoBundle”, which demonstrates the basic API functionality, but let us create our own model!
As an example, we should create a simple API for managing music Artists. Every Artist will have the following fields:
Symfony has a simple generate command, which will create the entity for us:
php app/console generate:doctrine:entity
It will ask you to specify the entity shortcut name, answer with AcmeDemoBundle:Artist
.
Then choose yml
as configuration format and create all the fields described above.
Do not generate empty repository class and simply confirm the generation at last step.
Your new class is almost ready to use, we just need to update the database schema:
php app/console doctrine:schema:update --force
Now, let’s configure our new resource! In the app/config/api.yml
file, add the following lines:
sylius_resource:
resources:
...
acme.artist:
driver: doctrine/orm
classes:
model: Acme\DemoBundle\Entity\Artist
Last step is to configure routing in app/config/routing.yml
file:
acme_artist:
resource: acme.artist
type: sylius.api
That’s all. Your resource is ready to use via API, give it a try:
Creating new artists is as simple as calling:
curl -i -X POST -H "Content-Type: application/json" -d '{"name": "Eminem", "biography": "Pure Awesomeness", "publishedAt": "2014-01-2015"}' http://localhost:8000/artists/
You should receive the following response:
HTTP/1.1 201 CREATED
Content-Type: application/json; charset=utf-8
{
"id": 1,
"name": "Eminem",
"biography": "Pure Awesomness",
"featured": false,
"published_at": "2014-01-25T00:00:00+0000"
}
You can get a single artist by the id:
curl -i http://localhost:8000/artists/1
You should receive the following response:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"id": 1,
"name": "Eminem",
"biography": "Pure Awesomness",
"featured": false,
"published_at": "2014-01-25T00:00:00+0000"
}
Did I mention that also supports XML format?
curl -i -H 'Accept: application/xml' http://localhost:8000/artists/1
You should see this:
HTTP/1.1 200 OK
Content-Type: application/xml; charset=utf-8
<?xml version=”1.0” encoding=”UTF-8”?>
<artist>
<id>1</id>
<name><![CDATA[Eminem]]></name>
<biography><![CDATA[Pure Awesomness]]></biography>
<featured>true</featured>
<published_at>2014-01-25T00:00:00+0000</published_at>
</artist>
To get a paginated list of artists, you can simply call:
curl -i http://localhost:8000/artists/
You should receive the following response:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"page": 1,
"limit": 10,
"pages": 1,
"total": 1,
"_links": {
"self": {
"href": "/artists/?page=1&paginate=10"
},
"first": {
"href": "/artists/?page=1&paginate=10"
},
"last": {
"href": "/artists/?page=1&paginate=10"
}
},
"_embedded": {
"items": [
{
"id": 1,
"name": "Eminem",
"biography": "Pure Awesomness",
"featured": false,
"published_at": "2014-01-25T00:00:00+0000"
}
]
}
}
To update an artist, simply send the following request:
curl -i -X PUT -H "Content-Type: application/json" -d '{"name": "Slim Shady", "biography": "Pure Awesomeness", "publishedAt": "2014-01-02"}' http://localhost:8000/artists/1
You should receive the following response:
HTTP/1.1 204 NO CONTENT
The API also supports partial updates:
curl -i -X PATCH -H "Content-Type: application/json" -d '{"publishedAt": "2014-02-05"}' http://localhost:8000/artists/1
You should receive the following response:
HTTP/1.1 204 NO CONTENT
To remove the Artist, simply call:
curl -i -X DELETE http://localhost:8000/artists/1
You should receive the following response:
HTTP/1.1 204 NO CONTENT
That was a good start. Now let us take the API to the next level and get more control about how our data is serialized.
Before we configure the serializer, you need to create a new Artist, cause we have deleted the first one!
curl -i -X POST -H "Content-Type: application/json" -d '{"name": "Elvis", "biography": "Still Alive", "publishedAt": "2010-12-24"}' http://localhost:8000/artists/
Create a simple YAML file in path src/Acme/DemoBundle/Resources/config/serializer/Entity.Artist.yml
:
Acme\DemoBundle\Entity\Artist:
exclusion_policy: ALL
xml_root_name: artist
properties:
id:
expose: true
type: integer
xml_attribute: true
name:
expose: true
type: string
biography:
expose: true
type: string
featured:
expose: true
type: boolean
serialized_name: is_featured
This will modify our XML responses a bit and change the field name for “featured” field.
You may need to clear the cache to apply the changes:
php app/console cache:clear
Now, call your API again:
curl -i http://localhost:8000/artists/2
The response should look like this:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"id": 2,
"name": "Elvis",
"biography": "Still Alive",
"is_featured": false
}
You can also configure HATEOAS links:
Acme\DemoBundle\Entity\Artist:
exclusion_policy: ALL
xml_root_name: artist
properties:
id:
expose: true
type: integer
xml_attribute: true
name:
expose: true
type: string
biography:
expose: true
type: string
featured:
expose: true
type: boolean
serialized_name: is_featured
relations:
- rel: self
href:
route: acme_api_artist_show
parameters:
id: expr(object.getId())
Your API will be even better now:
curl -i http://localhost:8000/artists/2
The response should look like this:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"id": 2,
"name": "Elvis",
"biography": "Still Alive",
"is_featured": false,
"_links": {
"self": {
"href": "/artists/2"
}
}
}
Links are also available on the resources index:
curl -i http://localhost:8000/artists/
The response should look like this:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"page": 1,
"limit": 10,
"pages": 1,
"total": 1,
"_links": {
"self": {
"href": "/artists/?page=1&paginate=10"
},
"first": {
"href": "/artists/?page=1&paginate=10"
},
"last": {
"href": "/artists/?page=1&paginate=10"
}
},
"_embedded": {
"items": [
{
"id": 2,
"name": "Elivs",
"biography": "Still Alive",
"is_featured": false,
"_links": {
"self": {
"href": "/artists/2"
}
}
}
]
}
}
OK, but our music artists have some albums, right? Let us add another model Album.
Symfony will generate it for us:
php app/console generate:doctrine:entity
Use AcmeDemoBundle:Album
as shortcut name, yml
as configuration format.
Our configuration needs to be updated in app/config/api.yml
file:
sylius_resource:
resources:
...
acme.album:
driver: doctrine/orm
classes:
model: Acme\DemoBundle\Entity\Album
Update routing in app/config/routing.yml
file:
acme_album:
resource: acme.album
type: sylius.api
Last step is to add the relation between Artist and Albums. First, let’s add a property and method to our new Album class.
<?php
namespace Acme\DemoBundle\Entity;
class Album
{
// ...
private $artist;
// ...
public function getArtist()
{
return $this->artist;
}
public function setArtist(Artist $artist)
{
$this->artist = $artist;
}
}
Now we need to tell Doctrine about this new relation, by updating the Album.orm.yml
file in src/Acme/DemoBundle/Resources/config/doctrine
. Add the following lines (manyToOne node):
Acme\DemoBundle\Entity\Album:
# ...
manyToOne:
artist:
targetEntity: Acme\DemoBundle\Entity\Artist
joinColumn:
name: artist_id
referencedColumnName: id
That is all! Now you should update the database schema:
php app/console doctrine:schema:update --force
You can now pass the Artist id as parameter:
curl -i -X POST -H "Content-Type: application/json" -d '{"title": "Encore", "description": "Really Good", "releaseDate": "2004-11-16", "artist": 2}' http://localhost:8000/albums/
You should receive the following response:
HTTP/1.1 201 CREATED
Content-Type: application/json; charset=utf-8
{
"id": 1,
"title": "Encore",
"artist": {
"id": 2,
"name": "Elvis",
"biography": "Still Alive",
"is_featured": false,
"_links": {
"self": {
"href": "/artists/2"
}
}
},
"release_date": "2004-11-16T00:00:00+0000",
"description": "Really Good"
}
Simple, isn’t it?
You can also very easily define your own routes. In app/config/routing.yml
, add the following route:
# app/config/routing.yml
acme_api_artist_albums:
path: /artists/{id}/albums/
defaults:
_controller: acme.controller.album:indexAction
_sylius:
filterable: true
criteria:
artist: $id
Now you can call /artists/:id/albums/
to get a paginated list of albums for specific artist.
curl -i http://localhost:8000/artists/2/albums/
You should receive the following response:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"page": 1,
"limit": 10,
"pages": 1,
"total": 1,
"_links": {
"self": {
"href": "/artists/1/albums/?page=1&paginate=10"
},
"first": {
"href": "/artists/1/albums/?page=1&paginate=10"
},
"last": {
"href": "/artists/1/albums/?page=1&paginate=10"
}
},
"_embedded": {
"items": [
{
"id": 1,
"title": "Encore",
"artist": {
"id": 2,
"name": "Elvis",
"biography": "Still Alive",
"is_featured": false,
"_links": {
"self": {
"href": "/artists/2"
}
}
},
"release_date": "2004-11-16T00:00:00+0000",
"description": "Really Good"
}
]
}
}
You can simply use the standard Symfony validation method and add mapping to your entity. Every album must have a title:
<?php
namespace Acme\DemoBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class Album
{
// ...
/**
* @Assert\NotBlank()
*/
private $title;
// ...
}
Now try to create an album without title specified:
curl -i -X POST -d "description=Not+Good+Album&releaseDate=2004-11-10&artist=2" http://localhost:8000/albums/
You should get the following response:
HTTP/1.1 400 BAD REQUEST
Content-Type: application/json; charset=utf-8
{
"code": 400,
"message": "Validation Failed",
"errors": {
"children": {
"title": {
"errors": [
"This value should not be blank."
]
},
"description": [
],
"releaseDate": [
],
"artist": [
]
}
}
}
Make sure to star Lionframe on GitHub and follow @Lakion on Twitter!
Lionframe can do a lot more and we will be constantly updating & improving this documentation, but in the meantime you can have a look at SyliusResourceBundle’s documentation. Enjoy!