Chapter 4. The First Request/Response – Building the User Wall

In this chapter, we will focus on the wall of the user. We will cover the changes we should make on the API level to provide the wall data as well as the code we need on the client side to display the user wall. As this is the first section of the social network we implement, we will also cover the basic access to the database. We will assume for now that the data is already in the database; we don't care how the data arrived there because our only job here is to show that data on the user wall. This means that the API will provide, for now, just the access to the data and the frontend will show the data. It is really simple and a good exercise to check how the frontend and API integrate and work together.

By the end of this chapter, you will know how the frontend and API work together to display a page on the social network. Also, you will understand how we manage to connect to the database and get the data we need.

API development

As we are working with the API-centric architecture, we should first develop the API code in order to provide data to the clients. This is the general approach that we will follow through the book to develop any section of the social network.

In the previous chapter, we saw that ZF2 provides two controller types we can use. The first one called AbstractActionController will be used on the client side to develop the client itself. The second type, AbstractRestfulController, is the one we will use on the API side to build a RESTful web service to provide access to the data and functionalities.

Requirements

For the API level of the wall, we need to use a new endpoint. In this case the API will expose the data using the URI, /wall/:id. Let's see a more formal description of this end-point and the functionality provided.

HTTP method

Controller method

Parameters

Functionality

GET

get()

getList()

id

This is the only method providing data to the world. The id parameter is mandatory and should contain the username. This method will return all the info related to the user wall.

As the id is mandatory, getList() will return an HTTP error 405 Method Not Allowed.

POST

create()

$data

This method is not allowed.

PUT

update()

id

This method is not allowed.

DELETE

delete()

id

This method is not allowed.

As you can see, get() is the only method allowed at this endpoint. This method will return the data related to the profile as a JSON encoded string. In case the user is not found, this method will return an HTTP 404 error.

The API will always return a JSON encoded string as a result of the requests even if we are dealing with errors; this means that the API code will not have a view per se.

Working with the database

The first task we have to complete is the database work. We will need to create a table to store the data of the user. In order to create the table on the virtual machine, we need to connect to the database server and then create the table in the database. The first task is really easy because the MySQL port is open to the host machine.

Open your favorite SQL editor/manager and connect to the server using the IP of the VM. If you haven't made changes to the IP on the Vagrantfile, it should be 192.168.56.2. As a username you should use root and as the password you must use the one specified in Vagrantfile. If you haven't changed it, it should also be root.

If you prefer to use the command-line interface to connect to the MySQL server, it will be like the following command line:

mysql –uroot –proot –h192.168.56.2

Once you connect, you should be able to see a database named sn.

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sn                 |
| test               |
+--------------------+
5 rows in set (0.00 sec)

Now let's create a table named users inside the sn database. This table will contain the following fields:

  • Id
  • Username
  • Password
  • Email
  • Avatar_id
  • Name
  • Surname
  • Bio
  • Location
  • Gender

The create table statement is as follows:

CREATE TABLE `users` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `username` varchar(50) DEFAULT NULL,
  `email` varchar(254) DEFAULT NULL,
  `password` binary(60) DEFAULT NULL,
  `avatar_id` int(11) unsigned DEFAULT NULL,
  `name` varchar(25) DEFAULT NULL,
  `surname` varchar(50) DEFAULT NULL,
  `bio` tinytext,
  `location` tinytext,
  `gender` tinyint(1) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_username` (`username`),
  KEY `idx_email` (`email`(191))
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

As you can see, gender is a tinyint value; in this case we need a convention to express male and female. We will use 1 for male and 0 for female.

Let's take an overview on how ZF2 manages to connect and retrieve data from a database.

There are three main components involved with databases: ZendDbAdapter, ZendDbTableGateway, and ZendDbRowGateway.

ZendDbAdapter

This is the most important subcomponent of ZendDb collection of objects. This object translates the queries we create on the code to vendor- and platform-specific code and takes care of all the differences between them. It creates an abstraction layer in the databases and provides a common interface.

When creating an instance of this object, we should provide minimum information such as the driver to use, the database name to connect, and username and password for the connection itself. After that we can use this object to query the database directly but, as mentioned, ZF2 provides two more objects that will ease the process.

ZendDbTableGateway

This object is a representation of a table on the database and provides the usual operations we would like to do over a table such as, select(), insert(), delete(), or update(). This part of the library provides two implementations of TableGatewayInterface: AbstractTableGateway and TableGateway. Two important things we should know about this component is that we can configure it to return each row in a RowGateway object or provide a class that will act as an entity and will store the row data.

The other interesting functionality we should be aware of is the Features API. This internal API allows us to extend the functionality of the TableGateway object without the need to extend the class and customize the behavior to meet our needs. As usual ZF2 provides a few built-in features we can take advantage of:

  • GlobalAdapterFeature: This feature allows us to use a global adapter without the need to inject it into the TableGateway object.
  • MasterSlaveFeature: This feature modifies the behavior of TableGateway to use a specific master adapter for inserts, updates, and deletions and a slave adapter for select operations.
  • MetadataFeature: This feature gives us the ability to feed TableGateway with the column information provided by a Metadata object.
  • EventFeature: As you can imagine, this allows us to use events in TableGateway and subscribe to events of the TableGateway lifecycle.
  • RowGatewayFeature: This modifies the behavior of TableGateway when executing select statements and forces the object to return a ResultSet object. When iterating the ResultSet object, we will be working with the RowGateway objects.

ZendDbRowGateway

Following the concept of TableGateway, RowGateway represents a row inside the table of the database. This object also provides some methods related to the actions we usually make over a row, such as save() and delete(). As we mentioned before if you use RowGatewayFeature over TableGateway, the result of the select queries will be a ResultSet object containing instances of this object. Each object will contain the data of the row it represents and we will be able to manipulate it, change it, and save it back to the database, everything using the same object.

Setting up the connection

Now we have a clear understanding about each object involved in the database connection, let's set up the code needed to actually connect to the database.

The first step is providing the information needed by ZendDbAdapter. We will do this in two different config files: global.php and local.php. You can find them in the config folder; the first one will be there and the second one should be created manually. The reason why we use two files is because local.php will be ignored by git as stated in the .gitignore file.

You can see in the folder that this allows us to put the general configuration that will be committed on the global.php file, and keep the private data such as usernames and passwords on the local.php file that will not be committed.

return array(
    'db' => array(
        'driver' => 'Pdo',
        'dsn' => 'mysql:dbname=sn;host=localhost',
        'driver_options' => array(
            PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES 'UTF8''
        ),
    ),
    'service_manager' => array(
        'factories' => array(
            'ZendDbAdapterAdapter' => 'ZendDbAdapterAdapterServiceFactory',
        ),
    ),
);

This is the config data you should put in the global.php file. Here, we are specifying a new entry called db and inside that we define the options: driver, dsn, and driver_options. As you can see, all the data is pretty straightforward.

Note

When connecting to databases, ZF2 is able to use different drivers; the options available are Mysqli, Sqlsrv, Pdo_Sqlite, Pdo_Mysql, and Pdo. If you want to know more about the options available to connect to databases, you can check out the documentation at http://framework.zend.com/manual/2.0/en/modules/zend.db.adapter.html.

The second block of config is related to ServiceManager. We are registering a new service called ZendDbAdapterAdapter and we are pointing it to AdapterServiceFactory of the Db package.

Let's see the data we should add on the local.php file. Keep in mind that our VM is running with the skip-grant-tables options so these credentials will not take effect but it's a good practice to define the data on the config file.

return array(
    'db' => array(
        'username' => 'root',
        'password' => 'root',
    ),
);

As you can notice we are extending the db config section and adding the username and password for the db connection.

ViewManager configuration

The API response will always be text. That's the reason we are going to encode every response as JSON. There are a few ways to accomplish this task and we will see the most straightforward and easy. The View component in Zend is created with flexibility in mind and provides a way to modify how the view works using strategies.

For our case, ZF2 provides us with a strategy called ViewJsonStrategy. Basically this component attaches two methods to the EVENT_RENDERER and EVENT_RESPONSE events of the view and modifies the way the view works based on the data we send to the view. To make ViewJsonStrategy work, we should return the JsonModel objects on our controllers. The JsonModel objects will be picked up by ViewJsonStrategyand. We will take care of setting up the correct Content-Type header. After that the component will use the provided JsonModel object to get a serialized version of the data to be sent as a response.

To activate this strategy and start returning JSON data, we only have to add the following four lines to the global.php file:

    'view_manager' => array(
        'strategies' => array(
            'ViewJsonStrategy',
        ),
    ),

Creating the Users module

This will be the first module we create and is only going to contain the TableGateway object for the table that we just created on the database. Before starting to write the code, we need to create a new module with the following structure:

Creating the Users module

Creating the UsersTable.php file

Now that we have the folder structure, the next step is to create a TableGateway object in order to be able to fetch the data from the database. Let's start by creating a file called UsersTable.php inside the Model folder and put the following code inside:

<?php
namespace UsersModel;

use ZendDbAdapterAdapter;
use ZendDbTableGatewayAbstractTableGateway;
use ZendDbAdapterAdapterAwareInterface;

class UsersTable extends AbstractTableGateway implements AdapterAwareInterface
{
    protected $table = 'users';
    
    public function setDbAdapter(Adapter $adapter)
    {
        $this->adapter = $adapter;
        $this->initialize();
    }
    
    public function getByUsername($username)
    {
        $rowset = $this->select(array('username' => $username));
        
        return $rowset->current();
    }
}

As this is the first TableGateway we created, let's have a closer look at it. First thing we should do is declare the namespace where this class resides. After that we have to declare the components we are going to use inside the class using the use keyword.

The class is extending AbstractTableGateway and implementing the interface, AdapterAwareInterface. This interface allows the dependency injector to set the adapter we have to use for the connections to the database. As we are extending AbtractTableGateway, we have to define the name of the table we are representing; that's the job we do when we declare the protected variable, $table. After that we arrive at the first method. This is just the implementation of the method stated in the interface. In this case we store the adapter on the local variable, and then we call the initialize() method to set up the wiring on the object.

Finally, we define a custom method that we will use to get info from the table. We need to be able to retrieve users by their username and that's the job of the last method on this class. getByUsername() uses the internal select() method by passing an array as an argument. This array basically is the where condition for the select statement and in this case we are just filtering by the username column. As we are fetching the info of one user, instead of returning Rowset we call the current() method to return the first result. In theory we are going to have only one user with a specific username because we defined a unique index on the username column.

Module configuration

Let's now have a look at the contents of the configuration file.

return array(
    'di' => array(
        'services' => array(
            'UsersModelUsersTable' => 'UsersModelUsersTable'
        )
    ),
);

As we are just storing the TableGateway object, the only entry on the config file is to define the availability of this object on the dependency injector.

Note

Dependency injection is a software design pattern that allows the developer to remove the hard-coded dependencies and makes it possible to swap them with different ones at runtime.

The module.php file

As we have seen before in other classes, we have to define the current namespace and import the classes we are going to use; in this case the code is as follows:

namespace Users;

After that we define a class called Module and then we jump to the first method, getConfig() used by ModuleManager to retrieve the config file of the module. The method looks like the following code snippet:

public function getConfig()
{
    return include __DIR__ . '/config/module.config.php';
}

As you can see we are just returning the array contained in the configuration file for the module. We will later explore the contents of this file.

We already saw that ZF2 is PSR-0 compliant and has its own auto-loading mechanism using the ZendLoader components. The last method we declare on the Module class is called getAutoloaderConfig() and it's used to tell the autoloader where it can find the files for this module.

public function getAutoloaderConfig()
{
    return array(
        'ZendLoaderStandardAutoloader' => array(
            'namespaces' => array(
                __NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
            ),
        ),
    );
}

Adding the Wall module

Our next task is to create the module that will contain the code related to the user wall. Take a look at the following folder structure and recreate it inside the module folder:

Adding the Wall module

Let's define the contents of the Module.php file that should be placed at the root level of the Wall module folder. Remember that this file is the configuration class used by ModuleManager to get extra information about our modules.

Module.php contents

As we saw before in the Users module, we have to define the current namespace and import the classes we are going to use. In this case, the code looks exactly the same as the Users/Module.php file and the only difference is the definition of the current namespace.

namespace Wall;

Module configuration

Let's now review the contents of the configuration file. The first section is related to the router and the routes we are listening to. We need to define a route to access the data and the controller/action that will fulfill the request. Let's see the following code snippet to know how it's configured:

'router' => array(
    'routes' => array(
        'wall' => array(
            'type' => 'ZendMvcRouterHttpSegment',
            'options' => array(
                'route'    => '/api/wall[/:id]',
                'constraints' => array(
                    'id' => 'w+'
                ),
                'defaults' => array(
                    'controller' => 'WallControllerIndex'
                ),
            ),
        ),
    ),
),

As you can see, this way of configuring the routes is very verbose, but it's also very convenient. We are defining a new route identified by the name, wall, of type ZendMvcRouterHttpSegment and we define the structure of the route with an optional parameter, the id of the user, which in our case will be the username. We also set up the constraint about what the id should look like. Finally, in the defaults section, we define the controller in charge for requests sent to this URL.

Let's now dig in the last section of the configuration file that basically specifies the controllers available on this module.

'controllers' => array(
    'invokables' => array(
        'WallControllerIndex' => 
        'WallControllerIndexController'
    ),
)

This section is as simple as defining an identifier for the controller and the class of the file containing the controller.

Adding the IndexController.php file

We have arrived at the last file of the API, the controller. Here, we put everything together and we fulfill the request with the data the clients need. As we said before, the controllers on the API side are based on AbstractRestfulController. That means the action we will execute to fulfill the request is determined based on the type of requests.

The first thing is to define the namespace where this file resides, and then the components we will use in the class.

namespace WallController;

use ZendMvcControllerAbstractRestfulController;
use ZendViewModelJsonModel;

Then we proceed to create the class itself extending AbstractRestfulController. The class will be called IndexController.

In this controller, we have to accomplish two things: the first one is to implement the abstract methods of the parent class and the second one is to have access to the UsersTable object. Let's see first how we get access to the UsersTable object.

protected $usersTable;

protected function getUsersTable()
{
    if (!$this->usersTable) {
        $sm = $this->getServiceLocator();
        $this->usersTable = $sm->get(UsersModelUsersTable'),
    }
    return $this->usersTable;
}

We define a new protected variable inside the class that will hold the UsersTable object. After that we create a new method called getUsersTable(), which will get the UsersTable object from ServiceManager and will store it in the internal variable.

Now let's implement the methods of the parent class. We have a bunch of them as we defined in the requirements. As we said, this controller will only respond to the get() method, the rest will not be used. So in that case we should follow the HTTP specification and issue a 405 code. Let's see how we deal with the methods that we don't use and then also see how we implement the get() method.

public function getList()
{
    $this->methodNotAllowed();
}

public function create($data)
{
    $this->methodNotAllowed();
}

public function update($id, $data)
{
    $this->methodNotAllowed();
}

public function delete($id)
{
    $this->methodNotAllowed();
}

protected function methodNotAllowed()
{
    $this->response->setStatusCode(
        endHttpPhpEnvironmentResponse::STATUS_CODE_405
    );
}

As you see the unused methods call the methodNotAllowed() method that will set the status code of the response to 405.

Let's see now how we tackle the get() method.

public function get($username)
{
    $usersTable = $this->getUsersTable();
    $userData = $usersTable->getByUsername($username);
        
    if ($userData !== false) {
        return new JsonModel($userData->getArrayCopy());
    } else {
        throw new Exception('User not found', 404);
    }
}

The first thing we do is retrieve the UsersTable object. After that, we call the getByUsername() method we created before. If we have data, it means that the user exists so we proceed to return a JsonModel object containing a copy of the data. In case the user is not in our database, we throw an exception that will be caught by a listener, which we will implement in the next section.

The API-Error approach

Usually when we develop APIs, we use the HTTP status codes to report errors that we encounter while processing the request. But this is not enough in most cases and we end up creating our own objects to represent an error status on the API level. To solve this issue and standardize the way we respond to errors on API based on JSON or XML, two proposals are gaining track and they use the error media types, such as application/api-problem+json and application/vnd.error+json.

In this section we will build the foundation of this approach but we will cover it in more depth and implement it properly later to avoid overloading the chapter with a lot of new stuff.

Adding the Common module

We will implement the API-Error listener in a new module to have the code separated by responsibility. Let's first create the folder structure. Then we will jump to the code we should add and how it works.

Adding the Common module

Modifying the application.config.php file

Now, the first thing we should do is add the new modules to the application.config.php file so the framework will know that they are available. In order to do that we need to modify the modules configuration like the following code snippet:

'modules' => array(
    'Wall',
    'Users',
    'Common'
),

As you can see we just add as many entries as we have modules and they are called after the module name.

Modifying the Module.php file

We have already seen that all the modules must have a file called Module.php. As we discussed earlier, this file is used to get extra configuration and information about the module and it's used by ModuleManager. For this module, we will add the usual code in our Module.php to return the config file and configure the autoloader. Also, we will initialize our listener when the module is being bootstrapped.

public function getConfig()
{
    return include __DIR__ . '/config/module.config.php';
}
    
public function getAutoloaderConfig()
{
    return array(
        'ZendLoaderStandardAutoloader' => array(
            'namespaces' => array(
                __NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__
            ),
        ),
    );
}
    
public function onBootstrap($e)
{
    $app = $e->getTarget();
    $services = $app->getServiceManager();
    $events = $app->getEventManager();
    $events->attach(
        $services->get('CommonListenersListener')
    );
}

The onBootstrap() method is called when ModuleManager is bootstrapping the module. We use this to configure the listener, adding it to EventManager.

Modifying the module.config.php file

Now that we have the first bits of our module and the manager knows what to do with it, let's review the contents of the module.config.php file. As you will see it is really simple.

return array(
    'service_manager' => array(
        'invokables' => array(
            'CommonListenersApiProblemListener' => 
            'CommonListenersApiProblemListener',
        ),
    ),
);

In this code, we are adding a new object as invokable to the service_manager array. So, we will be able to retrieve it later.

Adding the ApiErrorListener.php file

Finally, let's have a look at the listener that will help us spot issues on the API and will modulate the standard response for API-Problem in the future.

As usual, the first thing to do is declare the namespace and the components we are going to use.

namespace CommonListeners;

use ZendEventManagerAbstractListenerAggregate;
use ZendEventManagerEventManagerInterface;
use ZendMvcMvcEvent;
use ZendViewModelJsonModel;

Right after that we will define a new class called ApiErrorListener and we will make this class extend the AbstractListenerAggregate class, which will allow the class to attach one or more listeners.

class ApiErrorListener implements AbstractListenerAggregate

Now we should track the events we are listening to in order to accomplish our need to add a property to the class to store them. Then we should define the attach() method as required by the interface, ListenerAggregateInterface. We have to attach our methods to the events we are interested in by calling the attach() method of EventManager and passing the event name, the method to call, and the priority as parameters.

public function attach(EventManagerInterface $events)
{
    $this->listeners[] = $events->attach(
        MvcEvent::EVENT_RENDER, 
        'ApiErrorListener::onRender', 
        1000
    );
}

Finally, let's see the code that inspects the response and determines if there is an error and format it.

public static function onRender(MvcEvent $e)
{
    if ($e->getResponse()->isOk()) {
        return;
    }
        
    $httpCode = $e->getResponse()->getStatusCode();
    $sm = $e->getApplication()->getServiceManager();
    $viewModel = $e->getResult();
    $exception = $viewModel->getVariable('exception'),
        
    $model = new JsonModel(array(
        'errorCode' => $exception->getCode() ?: $httpCode,
        'errorMsg' => $exception->getMessage()
    ));
    $model->setTerminal(true);
        
    $e->setResult($model);
    $e->setViewModel($model);
    $e->getResponse()->setStatusCode($httpCode);
}

Let us go line by line in this big chunk of code. At the top we check if the response is marked as 200 OK. If it is, we don't have to continue worrying about errors; otherwise, we retrieve the status code and the exception variable from ViewModel. We are assuming that if something goes wrong, an exception will be thrown. That's why on our controller if the user is not in the database, we throw an exception because it will be caught here.

With the data we extracted, we create a new JsonModel object specifying errorCode based on the code used in the exception; otherwise, we use the HTTP error and we also specify errorMsg based on the text of the exception.

Then we use the setTerminal() method of the JsonModel object to set the terminal flag to true to tell ZF2 to not wrap the returned model in a layout.

The last section of the method takes care of attaching the new JsonModel object to the response overwriting the previous one and setting the corresponding status code.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset