What is Lionframe?

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.

Installation

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!

Adding Your First Resource

As an example, we should create a simple API for managing music Artists. Every Artist will have the following fields:

  • name (string)
  • biography (text)
  • featured (boolean)
  • publishedAt (date)

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:

POST A New Artist

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"
}

GET - Viewing A Single Artist

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>

GET - Paginating Over All Artists

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"
            }
        ]
    }
}

PUT - Updating A Single Artist

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

PATCH - Partial Update Of An Artist

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

DELETE - Removing Artist

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

Configuring Serialization

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
}

Hypermedia Links

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"
                    }
                }
            }
        ]
    }
}

Handling Relations

OK, but our music artists have some albums, right? Let us add another model Album.

  • title (string)
  • description (text)
  • releaseDate (date)

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

POST A New Album

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?

Your Custom Routing

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"
            }
        ]
    }
}

Validation

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": [
            ]
        }
    }
}

Summary

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!


Want to learn more?

This website uses cookies to improve your experience. We’ll assume you are ok with this, but you can opt-out if you wish, and read more about it or just close it.