Tuesday, October 21, 2014

Implementing Apigility and Symfony2 with MongoDB

I wrote this as a personal note but posting it now publicly hoping it will also benefit others who are looking to implement Apigility with Symfony2 and MongoDB. This implementation is based on Enrico Zimuel's http://www.zimuel.it/create-api-symfony2-apigility/.

Create a directory where you will place both symfony and apigility installations e.g.
1
2
/var/www/acmestore.local/public_html/apigility
/var/www/acmestore.local/public_html/symfony

Install Symfony

1
composer create-project symfony/framework-standard-edition /var/www/acmestore.local/public_html/symfony/ "2.3.*"

According to symfony manual, before running Symfony for the first time, execute the following command to make sure that your system meets all the technical requirements:

1
$ php app/check.php

Also make sure that both:

1
2
app/cache
app/logs
are writable.

Install Symfony DoctrineMongoDBBundle

Add the following to your symfony's composer.json


1
2
3
4
5
6
7
/var/www/acmestore.local/public_html/symfony/composer.json
{
    "require": {
        "doctrine/mongodb-odm": "1.0.*@dev",
        "doctrine/mongodb-odm-bundle": "3.0.*@dev"
    },
}

and install the dependencies by running Composer's update command from the directory where your composer.json file is located:


1
composer update doctrine/mongodb-odm doctrine/mongodb-odm-bundle

Register the annotations library by adding the following to the autoloader (below the existing AnnotationRegistry::registerLoader line):


1
2
3
// app/autoload.php
use Doctrine\ODM\MongoDB\Mapping\Driver\AnnotationDriver;
AnnotationDriver::registerAnnotationClasses();

Update your AppKernel.php file, and register the new bundle:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// app/AppKernel.php
public function registerBundles()
{
    $bundles = array(
        // ...
        new Doctrine\Bundle\MongoDBBundle\DoctrineMongoDBBundle(),
    );

    // ...
}

And setup the basic configuration


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# app/config/config.yml
doctrine_mongodb:
    connections:
        default:
            server: mongodb://localhost:27017
            options: {}
    default_database: test_database
    document_managers:
        default:
            auto_mapping: true

Sample Bundle: Store Bundle


1
php app/console generate:bundle --namespace=Acme/StoreBundle


Create the Document Class


1
2
3
4
5
6
7
8
9
// src/Acme/StoreBundle/Document/Product.php
namespace Acme\StoreBundle\Document;

class Product
{
    protected $name;

    protected $price;
}

Add Mapping Information


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// src/Acme/StoreBundle/Document/Product.php
namespace Acme\StoreBundle\Document;

use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;

/**
 * @MongoDB\Document
 */
class Product
{
    /**
     * @MongoDB\Id
     */
    protected $id;

    /**
     * @MongoDB\String
     */
    protected $name;

    /**
     * @MongoDB\Float
     */
    protected $price;
}

Generate corresponding Getters and Setters

1
php app/console doctrine:mongodb:generate:documents AcmeStoreBundle

Install Apigility

1
composer create-project -sdev zfcampus/zf-apigility-skeleton /var/www/acmestore.local/public_html/apigility/

Put it in development mode
1
2
cd path/to/install
php public/index.php development enable # put the skeleton in development mode

Make sure module/ and config/ are writable.

Important:
Add all the dependencies used by composer.json of your Symfony2 application

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
"symfony/symfony": "2.3.*",
"doctrine/orm": "~2.2,>=2.2.3",
"doctrine/doctrine-bundle": "1.2.*",
"twig/extensions": "1.0.*",
"symfony/assetic-bundle": "2.3.*",
"symfony/swiftmailer-bundle": "2.3.*",
"symfony/monolog-bundle": "2.3.*",
"sensio/distribution-bundle": "2.3.*",
"sensio/framework-extra-bundle": "2.3.*",
"sensio/generator-bundle": "2.3.*",
"incenteev/composer-parameter-handler": "~2.0",
"doctrine/mongodb-odm": "1.0.*@dev",
"doctrine/mongodb-odm-bundle": "3.0.*@dev"
and add the following

1
2
"autoload": {
        "psr-0": { "": "/var/www/acmestore.local/public_html/symfony/src/" }

to the composer.json of your Apigility installation and run composer update command.

Create your API (e.g. Store) and REST service (e.g. Product) and define the fields that correspond to the Product Document's fields you created earlier i.e. name and price.

In your Product REST service, under Settings -> REST Parameters, update the Hydrator Service Name field to "Zend\Stdlib\Hydrator\ClassMethods"

In Settings -> Service Class Names, update the Entity Class field to your Symfony's MongoDB Document class "Acme\StoreBundle\Document\Product"

Use your Symfony 2 MongoDB Document in your Apigility Resource

The idea is to bootstrap your Symfony 2 application in your Apigility's ProductResource class.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# /var/www/acmestore.local/public_html/apigility/module/Store/src/Store/V1/Rest/Product/ProductResource.php

namespace Status\V1\Rest\Status;

use ZF\ApiProblem\ApiProblem;
use ZF\Rest\AbstractResourceListener;
use Acme\StoreBundle\Document\Product;

class ProductResource extends AbstractResourceListener
{
    protected $dm;
    
    public function __construct()
    {
        $symfonyApp = '/var/www/acmestore.local/public_html/symfony';
        require_once $symfonyApp . '/app/AppKernel.php';
        
        $kernel = new \AppKernel('dev', true);
        $kernel->loadClassCache();
        $kernel->boot();
        
        $this->dm = $kernel->getContainer()->get('doctrine_mongodb')->getManager();
    }

And use the document manager in other methods of the ProductResource.php
e.g.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
   /**
     * Create a resource
     *
     * @param  mixed $data
     * @return ApiProblem|mixed
     */
    public function create($data)
    {
        //return new ApiProblem(405, 'The POST method has not been defined');
        
        $product = new Product();
        $product->setName($data->name);
        $product->setPrice($data->price);
        
        $this->dm->persist($product);
        $this->dm->flush();
        
        return $product;
    }

    /**
     * Fetch a resource
     *
     * @param  mixed $id
     * @return ApiProblem|mixed
     */
    public function fetch($id)
    {
        //return new ApiProblem(405, 'The GET method has not been defined for individual resources');
        
        $product = $this->dm->getRepository('AcmeStoreBundle:Product')->find($id);
        
        return $product;
    }

No comments:

Post a Comment