viernes, 13 de enero de 2012

My First RESTful Web Service with Symfony2 Part I

I want to create a simple Resftul Web service for reading data in JSON format. On part I I´ll discuss the basics on how to configure the web service on part II I´ll discuss how to introduce pagination to the web service.

Requirements:

  • PHP 5.3.6
  • Netbeans 7.0.1
  • Wamp 2.0

1   Installing Symfony2

  1. Download Symfony2 in zip format from http://symfony.com/download.
  2. Unzip the file. To your NetbeansProjects directory,
  3. Rename the symfony directory to mroute_rest_service.
  4. Open a command Window and move to the mroute_rest_service.directory.

    Run:

    php .\app\console --version

    Symfony version 2.0.9 - app/dev/debug

2   Creating the Bundle

  1. On the command window run:

    php .\app\console generate:bundle


Welcome to the Symfony2 bundle generator

Your application code must be written in bundles. This command helps
you generate them easily.

Each bundle is hosted under a namespace (like Acme/Bundle/BlogBundle).
The namespace should begin with a "vendor" name like your company name, your
project name, or your client name, followed by one or more optional category
sub-namespaces, and it should end with the bundle name itself
(which must have Bundle as a suffix).

Use / instead of \ for the namespace delimiter to avoid any problem.
  1. Fill as follows:

Bundle namespace: MyGIS/GISBundle

In your code, a bundle is often referenced by its name. It can be the
concatenation of all namespace parts but it's really up to you to come
up with a unique name (a good practice is to start with the vendor name).
Based on the namespace, we suggest MyGISGISBundle.

Bundle name [MyGISGISBundle]:

The bundle can be generated anywhere. The suggested default directory uses
the standard conventions.

Target directory [C:\NetbeansProjects\gis_rest_services/src]:

Determine the format to use for the generated configuration.

Configuration format (yml, xml, php, or annotation) [annotation]:

To help you getting started faster, the command can generate some
code snippets for you.

Do you want to generate the whole directory structure [no]?

Summary before generation

You are going to generate a "MyGIS\GISBundle\MyGISGISBundle" bundle
in "C:\NetbeansProjects\gis_rest_services/src/" using the "annotation" format.

Do you confirm generation [yes]?

Bundle generation


Generating the bundle code: OK
Checking that the bundle is autoloaded: OK
Confirm automatic update of your Kernel [yes]?
Enabling the bundle inside the Kernel: OK
Confirm automatic update of the Routing [yes]?
Importing the bundle routing resource: OK


You can now start using the generated code!

3   Generating Doctrine Entities

We are going to to have two entities Route and Points.

  1. On the command window run:

    php .\app\console gen:doctrine:entity

  2. On Entity shortcut name type MyGISGISBundle:MRoute
  3. Accept defualt configuration format annotation
  4. Add the fields as follows:
    1. Name

      Field name: name

      Field type: string

      Field length: 125

  5. On Do you want to generate an empty repository class answer yes
  6. On the command window run:

    php .\app\console gen:doctrine:entity

  7. On Entity shortcut name type MyGISGISBundle:Point
  8. Accept defualt configuration format annotation
  9. Add the fields as follows:
    1. Name

      Field name: name

      Field type: string

      Field length: 50

    2. X

      Field name: x

      Field type: decimal

    3. Y

      Field name: y

      Field type: decimal

    4. PType

      Field name: pType

      Field type: string

      Field length: 25

  10. On Do you want to generate an empty repository class answer yes

4   Opening the Project with Netbeans

Open Netbeans.

  1. Select File\New Project
  2. Select PHP Application with Existing Sources.
  3. Click Next.
  4. On the New PHP Application with Existing Sources
    1. Select the source folder where you unzipped the Symfony framework (C:\NetbeansProjects\mroute_rest_service).
    2. Select PHP Version: 5.3
    3. Click Next
  5. Click Finish.

5   Connecting to the Database

5.1   Connection Parameters

On Netbeans open C:\NetbeansProjectsmroute_rest_service\app\config\parameters.ini.

Edit the connection parameters as shown:


[parameters]
database_driver = pdo_mysql
database_host = localhost
database_port =
database_name = gisroutes
database_user = root
database_password =
5.2   Making the One to Many Relationship work on the Entities

I had to figure out how to se the relationship using annotations. I found an example in XML and adapted it [1]. Another thing I found out was that I had to manually set the precision and scale of the decimal fields.

  1. Edit the Point.php and add the route

    /**
    * @ORM\ManyToOne(targetEntity="MRoute", inversedBy="points")
    */
    private $route;

    ...
  2. On the Point.php edit the $x adn $y Column definitio to add precision and scale as show:

    /**
    * @var decimal $x
    *
    * @ORM\Column(name="x", type="decimal", precision=10, scale=3)
    */
    private $x;

    /**
    * @var decimal $y
    *
    * @ORM\Column(name="y", type="decimal", precision=10, scale=3)
    */
    private $y;
  3. Edit the MRoute.php and add the points

    use Doctrine\Common\Collections\ArrayCollection;
    ...
    class Route {
    ...

    /**
    * @ORM\OneToMany(targetEntity="Point", mappedBy="route")
    * @var ArrayCollection $points
    */
    private $points;

    ...

    public function __construct() {git

    $this->points = new ArrayCollection;
    }

    ...
  4. I found this cool hay to generate getters and setters [2]. The following command will create the getters and setters for the Point and Route entities.

    php appconsole doctrine:generate:entities MyGISGISBundleEntity

5.3   Creating the Database and Tables

To create the database run:

php app/console doctrine:database:create

To create the tables run:

php app/console doctrine:schema:update --force
5.4   Generating CRUD Controllers and Templates (Optional)

As far as the web service is concerned you don´t need t create the CRUD controllers. I´m creating them to add data.

  1. On the command window run:

    php .\app\console doctrine:generate:crud

  2. Fill like this:

    The Entity shortcut name: MyGISGISBundle:MRoute

    Do you want to generate the "write" actions [no]? yes

    Configuration format (yml, xml, php, or annotation) [annotation]:

    Routes prefix [/mroute]:

    Do you confirm generation [yes]?

    Confirm automatic update of the Routing [yes]?

Now we are ready to make our first test.

5.5   Viewing the Data on Apache

On Wamp´s Apache create an alias names route-rest-service pointing to C:\NetbeansProjects\mroute_rest_service.

Open a brower http://localhost/mroute-rest-service/web/app_dev.php/mroute/

6   Creating a RestFul Web Service

I found an example on how to configure the routing usting yml [3] and I adapted it to annotations.

What we want is to get a list of routes with their points in json format.

First thing we need to do is add a method that will fetch the entities from the database. The following code includes the pagination concept wich we will discuss on Part II.

  1. Open src\MyGIS\GISBundle\Entity\MRouteRepository.php
  2. Add the following code whitin the class

    public function getMRoutes($order_by = array(), $offset = 0, $limit = 0) {
    //Create query builder for languages table
    $qb = $this->createQueryBuilder('m');
    //Show all if offset and limit not set, also show all when limit is 0
    if ((isset($offset)) && (isset($limit))) {
    if ($limit > 0) {
    $qb->setFirstResult($offset);
    $qb->setMaxResults($limit);
    }
    //else we want to display all items on one page
    }
    //Adding defined sorting parameters from variable into query
    if (isset($order_by)) {
    foreach ($order_by as $key => $value) {
    $qb->add('orderBy', 'l.' . $key . ' ' . $value);
    }
    }
    //Get our query
    $q = $qb->getQuery();
    //Return result
    return $q->getResult();
    }
  3. Open src\MyGIS\GISBundle\Controller\MRouteController.php
  4. Add the following code:

    /**
    * Deletes a MRoute entity.
    *
    * @Route("/routing-info/info.json", name="mroute_json_list")
    * @Method("get")
    */
    public function routingInfoAction() {
    $page = 1;

    $count = 25;
    $em = $this->getDoctrine()->getEntityManager();
    $mroutes = $em->getRepository('MyGISGISBundle:MRoute')->getMRoutes();
    $r_array = $this->routes2Array($mroutes);
    $r = array('page' => $page, 'count' => $count, 'routes' => $r_array);
    return new Response(json_encode($r));
    }

    private function routes2Array($routes){
    $routes_array = array();

    foreach ($routes as $route) {
    $points_array = array();
    foreach ($route->getPoints() as $point){
    $p_array = array('id' => $point->getId(), 'name' => $point->getName(),
    'x' => $point->getX(),'y' => $point->getY(), 'ptype' => $point->getPType());
    $points_array[] = $p_array;
    }
    $r_array = array('id' => $route->getId(), 'name' => $route->getName(), 'points' => $points_array);
    $routes_array[] = $r_array;
    }
    return $routes_array;
    }
  5. You´ll have to add some data to the se the web service working. Since I haven´t figured out how to install the FixtureBundle from behind a proxy I add the data via sql using phpMyAdmin. Use the SQL in https://gist.github.com/1594781 to add data.
  6. Run http://localhost/mroute-rest-service/web/app_dev.php/mroute/routing-info/info.json from your browser and voila it works!!

7   References

[1]
  1. Association Mapping http://www.doctrine-project.org/docs/orm/2.0/en/reference/association-mapping.html#one-to-many-bidirectional
[2]Databases and Doctrine ("The Model") http://symfony.com/doc/current/book/doctrine.html
[3]Routing http://symfpony-project.org/

1 comentario: