Chapter 4. Resource-Oriented Services

Resource Oriented Services are services that are designed in accordance with the REST architectural principles. As we discussed in the first chapter, the concept of a resource is at the heart of REST principles. Every service is a resource with a unique identifier. Hence the term "Resource Oriented".

In this chapter, we will study in detail and from ground-up how to design and implement services to comply with REST architectural principles. We will use a real world example, a simplified library system to learn from scratch how to design a system with REST principles in mind.

Designing Services

While designing services in compliance with REST architectural principles, the initial steps to be followed are not much different from those that would be followed architecting a software system with other techniques. First and foremost, the real world problem to be solved or the set of goals to be achieved with the system needs to be understood well. Often this is done through a requirements specification. The simplest requirements specification could be to define the problem in writing, using a natural language like English. May be a paragraph or two could be used to describe the problem.

Then the problem needs to be analysed and the problem can be understood by software architects. This is called requirements analysis. In the requirements analysis phase, we need to understand the data sets that exist within the systems and the business operations that are related to those data sets. Business operations in this context refer to the functions that implement the problem domain-specific logic. As an example, if we are to implement a library system, borrowing a book and returning a book are two business operations of the system. The business operations process the data sets to yield value added data or information. Therefore, during the requirements analysis phase, we would also have to analyse the databases that

would be used for the system. If we are to use existing databases, we would have to analyse and understand where the data would come from that is the data endpoints. If the databases are not already available, then the analysis phase would lead us to the information on what databases are to be designed for the system.

Given a problem description, we can look for the nouns to identify data sets. In the REST architectural style, data sets or nouns in the problem description turns out to be the resources. The data sets are so fundamental in the system that they need to be properly evaluated. Common groupings are important to not create redundancy. It is also important to have a flexible foundation for the system so that there is room for expansion later on, as the system evolves.

Once the resources are identified, we need to do some designwork on those resources. First, the resources need to be named. This is quite straightforward. We used the nouns to identify resources and those nouns themselves could be used to name the resources. Once the resources are identified, we can identify those resources with common attributes and group them into collections of resources.

The next step is to map URIs to each and every business operation of the resources. We can use a table structure to tabulate the resource and business operations against the URIs.

Sometimes, the business operations require some algorithmic parameters. Query variables can be used for algorithmic resources and the names and possible value domains for query variables need to be identified. However, note that the use of query parameters to differentiate between resources and business operations are highly discouraged in REST architectural principle.

In addition to the URI mappings, the HTTP verbs are also important for business operations. A single resource URI could have multiple HTTP verbs associated depending on the semantics of the operations.

Now that we have had a look into the steps involved with the service design in a resource-oriented world, let's look into an example on how to design a resource-oriented system.

Simplified Library System

Here is a problem statement that describes the library system that we are going to use for the example:

"The library contains a wide array of books. It may have several copies of a given book. Any library member may borrow books for three weeks. Members of the library can borrow up to two books at a time. New books arrive regularly. The system must keep track of when books are borrowed and returned by members."

This is a very simplified description of a library system. However, this is a good enough problem for us to explore resource-oriented service principles.

Resource Design

As mentioned earlier, we can look for nouns in the problem description in search of resources. If you read the problem statement given earlier, there are two nouns that stand out.

  • Book

  • Member

So, books and members are the two primary resource collections in the library system.

Next, what are the main business operations in this system? If we consider a book, the key business operations are:

  • Add new book

  • List books

  • Retrieve book

  • Update book

  • Remove book

We can map these operations to HTTP methods and URI combinations, as shown in the following table.

HTTP Method

URI

Description

GET

/book

List books

POST

/book

Create book(s)

GET

/book/1

Retrieve book

PUT

/book/1

Update book

DELETE

/book/1

Remove book

All of the above listed operations are related to the resource book. The first two operations operate on the resource collection. The last three operations in this example operate on a particular resource, the book ID 1. The ID in the URL is not really a parameter, rather the whole URL, including the ID uniquely identify the particular resource, the book with ID 1.

Note that we have used the URL prefix /book and not /books as the prefix for resource locations related to book resource. This is because we deal with a single resource instance in most of the cases when it comes to operations. As an example, we would update the book with the ID 1, and the URL would read /book/1, and this naturally reads as "update the book with ID 1". If we had used /books/1, it would have to be read, "from books, update the book with ID 1". Since we want to use the same prefix for all the operations related to the book resource, even for listing books we use a /book. We could have used /books only for operation, however, then we would lose the conceptual grouping of operations related to book resource, because some operations would use a different URL format.

It is also important to note that one must use the correct HTTP verb to match the semantics of each operation.

PUT vs POST

At a higher level, we can think that anything that creates a new resource is a PUT operation and anything that changes an existing resource is a POST operation. However, according to the HTTP RFC 2616 (http://www.w3.org/Protocols/rfc2616/rfc2616.html), PUT puts a page at a specific URL. If there's already a page there, it's replaced. If there is no page there, a new one is created. As an example, if a PUT request is sent to /book/1, then it would check to see if that is already available and act accordingly. If one needs to update the book with ID 2, then /book/2 must be used. While sending a PUT request, you need to be more specific on the resource identification. Even though, in theory, it is possible to send a PUT request to a generic URL like /book, where you indicate that every book needs to be updated, in practice it becomes a complicated operation. Moreover, you would want to prevent such uses, because a user by mistake could delete all the books from the system, if such an operation was possible, even with proper security authentication and authorization mechanisms in place

POST sends some data to a specified URL and as per the HTTP specification. The server can do whatever it wants with this POST data. It can store it somewhere privately, it can store it in the page at the URL that was POSTed to, it can store it in a new page, it can use it as input for several different existing and new pages, or it can throw the information away.

In the real world, POST is more often used than PUT. The main reason for this is the fact that the PUT operation would be disabled by the service provider, especially in a shared hosting environments, for security reasons. Hence, we can afford to use POST to create as well as to update resources, however in theory, it may look incorrect.

URI Design

For the library system that we are using as the example, we identified two key resources, book and member.

For operating on a particular book, we can use the book ID, the entity attribute that can be used to uniquely identify a resource instance. Likewise, we can use the member ID for members. We can append these attributes to the URI to help uniquely identify the resource instances.

Books /book/{book_id}
Member /member/{member_id}

The above two URI patterns can be used for operations related to the two key resources in our example problem domain. Combined with HTTP verbs, they can cater for the CRUD(Create,Read, Update and Delete) operations for the resources.

There are some operations that are a bit more complex than the style of operations described above that involve more than a single resource. As an example, consider a member borrowing a book. There are two resources involved in that operation, a member and a book, so does the return book operation. The borrow and return operations need to identify both the member and the book involved uniquely. The following URI patterns cater for those requirements.

Borrow /member/{member_id}/books/{book_id}
Return /member/{member_id}/books/{book_id}

To indicate that the member with ID 10 wants to borrow the book with ID 3, we can use:

/member/10/books/3

Similarly, if the same member borrows book the book with ID 7, we can use:

/member/10/books/7

For borrow and return operations, member is the primary resource. A member can borrow up to two books as per the problem statement, hence there can be more than one book associated with a member at a given time. Book is the secondary resource in these operations. Therefore the member appears first in the URI and the book appears second.

If you consider the data storage in a database for the borrow and return operations, it would use a combined primary key, consisting of the member ID and the book ID. However, while mapping these data to the resources in the REST style application, we would have to represent this information using a resource with a unique URI. In that case, we need to decide what appears first and what appears second in the URI.

If we are to interchange the ordering of where the member and book identifiers appear in the URI, then it would lead to ambiguities. Hence it would be a good practice to identify a primary resource. In the real world, a member would pick up a book from the shelf and walk to the counter to borrow it, or bring a borrowed book to the counter to return it. Hence we can say that it is the member who initiates the operations such as borrow and return of the books, and because of that we have chosen to make sure that member identifier appears first in the URI. When there are multiple resources involved with an operation, you could choose any ordering in the URI design to include them in the resource identifier. However, it always makes understanding and maintaining the system easier if we consider the real world scenarios while choosing the URI ordering.

Another important point to note on the design of the URI mapping is that we did not use the operation name in the URI. As an example, we could have used /borrow/member/X/books/Y/ or /return/member/X/books/Y/. While using REST, we operate on resources and the HTTP verbs we use to indicate the nature of the operation. Therefore, to understand the operations you have to consider both the URI as well as the HTTP verb used on that URI. The next section describes this URI and HTTP verb mapping.

URI and HTTP Verb Mapping

The final step of the resource-oriented service design is to come up with the mapping between URI and HTTP verb mapping for the business operations. The following table contains the mappings for the library system.

URI

HTTP Method

Collection

Operation

Business Operation

/book

GET

books

retrieve

Get books

/book

POST

books

create

Add book(s)

/book/{book_id}

GET

books

retrieve

Get book data

/member

GET

members

retrieve

Get members

/member

POST

members

create

Add member(s)

/member/{member_id}

GET

members

retrieve

Get member data

/member/{member_id}/books

GET

members

retrieve

Get member borrowings

/member/{member_id}/books/{book_id}

POST

members

create

Borrow book

/member/{member_id}/books/{book_id}

DELETE

members

delete

Return book

For both book and member resources, we have create and retrieve operations. We also have a retrieve operation for a particular member or a book. To keep the system simple, we do not have a delete operation for book and member resources. So the assumption is, once a book or a member is added to the system, the record stays there forever.

For a given member, we can retrieve the member borrowings, providing the member ID with HTTP GET verb.

We also have the borrow book and return book business operations designed. Using POST verb, with member ID and book ID in the URI, we have the borrow operation and the same URI with DELETE verb would represent the return operation.

System Implementation

Now that we have the initial RESTful design of the library system in place, let's go ahead and implement the system using PHP. The first step is to create a database and for this we will be using some SQL statements. The next section explains the steps involved in creating the database.

Library Database

We would need persistent data storage for storing the library system's data. We could use a file or a database for this purpose. Let us use a simple MySQL database for this. As a reminder, you will be able to get scripts for creating the database from the code download. This is available on www.packtpub.com.

Activate the MySQL PHP extension in php.ini configuration file before using the MySQL database.

The design of the database tables is straightforward as we already have identified the resources. In our service design steps, that we followed so far, we have identified book and member to be our two main resources. These are going to be represented in our database entity model as well. Hence we will have two entities, in other words two database tables, named book and member.

Next, when we consider storing data related to the borrowing of books, we notice that a given member can borrow more than one book. Also, we can notice that a given book could be borrowed by more than one member over time. Hence the borrowing of a book would relate books and members in a n:m relationship, meaning n number of books could relate to m number of members. This is known as the relationship cardinality in the entity relationship modelling of databases. When the relationship cardinality is many to many, or n:m, then we require a separate table to represent that relationship. Hence we require a table named borrowing to represent books borrowed by the members.

Following are the SQL create statements for the database table design that we will use for the sample implementation.

CREATE TABLE 'book' (
'id' int(11) NOT NULL auto_increment,
'name' varchar(256) NOT NULL,
'author' varchar(256) NOT NULL,
'isbn' varchar(256) NOT NULL,
PRIMARY KEY ('id')
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;
CREATE TABLE 'member' (
'id' int(11) NOT NULL auto_increment,
'first_name' varchar(256) NOT NULL,
'last_name' varchar(256) NOT NULL,
PRIMARY KEY ('id')
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;
CREATE TABLE 'borrowing' (
'member_id' int(11) NOT NULL,
'book_id' int(11) NOT NULL,
'start_date' date NOT NULL,
'end_date' date default NULL,
PRIMARY KEY ('member_id','book_id'),
KEY 'book_id' ('book_id'),
CONSTRAINT 'borrowing_ibfk_2' FOREIGN KEY ('book_id') REFERENCES 'book' ('id'),
CONSTRAINT `borrowing_ibfk_1` FOREIGN KEY ('member_id') REFERENCES 'member' ('id')
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

You can create a database named library in your MySQL database and create the tables within that database using the above SQL create statements. Note that both book and member tables have a field named id as the primary key, which is of type integer. The borrowing table would have the information of the books borrowed by a member. Note that the primary key of the borrowing table is the combination of the member ID and book ID, which are foreign keys of member and book tables. The foreign key constrains on the book_id and member_id ensures data integrity. On one hand, the constraint on book_id ensures that a member would not be able to borrow a book that is not registered in the book table. On the other hand, the constraint on the member_id ensures that a person not registered in the member table cannot borrow a book.

You can use MySQL manual at http://dev.mysql.com/doc/refman /5.0/en/index.html to learn more on MySQL and use phpMyAdmin (http://www.phpmyadmin.net/) tool to help you with database management.

The design of the borrowing table assumes that a member borrows a given book only once. In other words, once borrowed and returned, a member cannot borrow the same book. This again is done to keep the system simple but could be changed by changing the foreign key to include the borrowing start date. Note that this assumption was made to ensure that we keep the implementation simple so that we could focus more on the application of REST principles in the PHP sample implementation that follows.

Web Page from Data

You must be familiar with PHP and with MySQL programming. Use a tool and add a couple of books to the book table. You can either use a visual tool like phpMyAdmin or use the MySQL command line tool. You can use the following SQL statements to insert some test data to book table of the library database.

INSERT INTO `book`
VALUES (1,'Book1','Auth1','ISBN0001'),
(2,'Book2','Auth2','ISBN0002'),

Now you may be familiar with the techniques to pull data from a database and prepare an HTML page out of it. Just to recap, let's see how to display the book data with an HTML page. Note that this has nothing to do with the REST sample implementation we are going to look into, but it's just to remind you of some concepts.

<?php
// Connect to database
$link = mysql_connect('localhost', 'sam', 'pass') or die('Could not connect: ' . mysql_error());
mysql_select_db('library') or die('Could not select database');
// Prepare the query, and execute the query
$query = 'SELECT b.name, b.author, b.isbn FROM book as b';
$result = mysql_query($query) or die('Query failed: ' . mysql_error());
// Write the table headers
echo "<table border='1'>
";
$line = mysql_fetch_assoc($result);
if ($line == null)
return;
echo "	<tr>
";
foreach ($line as $key => $col_value) {
echo "		<td>$key</td>
";
}
echo "	</tr>
";
// Write the data into the table
mysql_data_seek($result, 0);
while ($line = mysql_fetch_array($result, MYSQL_ASSOC)) {
echo "	<tr>
";
foreach ($line as $key => $col_value) {
echo "		<td>$col_value</td>
";
}
echo "	</tr>
";
}
echo "</table>
";
// Free the results and close database connection
mysql_free_result($result);
mysql_close($link);
?>

This script, when accessed with a Web browser, would display a table like the following:

Web Page from Data

Now we are interested in implementing services rather than displaying data. However, the concepts used in this script are going to be useful while implementing services.

First we connect to the library database on the local machine with user name sam and password pass.

// Connect to database
$link = mysql_connect('localhost', 'sam', 'pass') or die('Could not connect: ' . mysql_error());
mysql_select_db('library') or die('Could not select database'),

We will use the above segment of code in our PHP service scripts to connect to the library database.

Then we prepare a query and execute that query. In this example, we select the book name, author and ISBN information.

// Prepare the query, and execute the query
$query = 'SELECT b.name, b.author, b.isbn FROM book as b';
$result = mysql_query($query) or die('Query failed: ' . mysql_error())

We will use similar statements to retrieve and store data to and from the library database in our service scripts.

Once the query is executed, we need to fetch data from the results structure.

$line = mysql_fetch_assoc($result);

We can use a while loop to fetch each line in the results set and deal with each result.

while ($line = mysql_fetch_array($result, MYSQL_ASSOC)) {
echo "	<tr>
";
foreach ($line as $key => $col_value) {
echo "		<td>$col_value</td>
";
}
echo "	</tr>
";
}

We will use a similar sequence of statements to fetch data in the service scripts.

Finally, it is always a good practice to clean-up stuff after we are done with our task. So free the result set and also close the database connection.

// Free the results and close database connection
mysql_free_result($result);
mysql_close($link);

Retrieve Operation

Let's first look at how to retrieve book information from the library service. As we have already discussed, the resource URI for book is /book. And with GET verb, we would return all the books we have.

Here is the PHP script to retrieve the book information.

<?php
$link = mysql_connect('localhost', 'sam', 'pass') or die('Could not connect: ' . mysql_error());
mysql_select_db('library') or die('Could not select database');
header("Content-Type: text/xml");
if ($_SERVER['REQUEST_METHOD'] == 'GET') {
// Handle GET request. Return the list of books.
$query = 'SELECT b.id, b.name, b.author, b.isbn FROM book as b';
$result = mysql_query($query) or die('Query failed: ' . mysql_error());
echo "<books>";
while ($line = mysql_fetch_array($result, MYSQL_ASSOC)) {
echo "<book>";
foreach ($line as $key => $col_value) {
echo "<$key>$col_value</$key>";
}
echo "</book>";
}
echo "</books>";
mysql_free_result($result);
}
mysql_close($link);
?>

The output generated is shown in following illustration. First we connect to the database, as explained in the previous section. Then we check if the request method is HTTP GET.

if ($_SERVER['REQUEST_METHOD'] == 'GET')

If the request method is GET, we execute the select query on the book table.

$query = 'SELECT b.id, b.name, b.author, b.isbn FROM book as b';
$result = mysql_query($query) or die('Query failed: ' . mysql_error());

For each line in the result, we create the XML output to be returned to the client from the service.

echo "<books>";
while ($line = mysql_fetch_array($result, MYSQL_ASSOC)) {
echo "<book>";
foreach ($line as $key => $col_value) {
echo "<$key>$col_value</$key>";
}
echo "</book>";
}
echo "</books>";

This script, when accessed with a Web browser, or a client that uses GET method, will return an XML document that will look like the following:

<books>
<book>
<id>1</id>
<name>Book1</name>
<author>Auth1</author>
<isbn>ISBN0001</isbn>
</book>
<book>
<id>2</id>
<name>Book2</name>
<author>Auth2</author>
<isbn>ISBN0002</isbn>
</book>
</books>

Next, we need to implement the retrieve operation for a given book. As per the URI pattern design, the request URI would contain the book ID and we need to retrieve the data for the book with given ID. An example request URL for get book data business operation would look like:

When we map the URI design to the implementation, both business operations get books and get book data would have to be served with book.php script. So we will add some logic to the script shown earlier to get book data for a given book.

Note that we are not using a query parameters to pass the ID of the book to the PHP script in this case. Rather, we are using the URI itself with a path separator character /. Passing parameters using ? followed by a query string is the functional programming style. It is similar to the concept of calling a function with a set of parameters. When designing resource-oriented services, it is better to focus on REST principles, where it is discouraged to use query parameters to get a job done. While using query parameters, we could lose track of resources and gradually the resource-oriented system could degenerate to a functional system.

First, we need to look into path elements to see if we have a book ID in the request path. This can be done by looking into the path information and splitting it with the / character.

// Check for the path elements
$path = $_SERVER['PATH_INFO'];
if ($path != null) {
$path_params = spliti ("/", $path);
}

If the request was sent to http://localhost/rest/04/library/book.php/2, then $_SERVER['PATH_INFO'] would be /2 and we will have $path_params[0] would be equal to 2. If you are using a web server like lighttpd, you may run into problems with $_SERVER['PATH_INFO']. Please have a look at http://trac.lighttpd.net/trac/wiki/TutorialLighttpdAndPHP to know the details on how to deal with such problems.

Knowing this, we can prepare the query based on the fact, if we have more parameters in the path information or not, to pick the book ID from path information.

if ($path_params[1] != null) {
$query = "SELECT b.id, b.name, b.author, b.isbn FROM book as b WHERE b.id = $path_params[1]";
} else {
$query = "SELECT b.id, b.name, b.author, b.isbn FROM book as b";
}

We use the $path_params[1] variable directly in the SQL string in the above example. However, this would lead to security problems such as SQL injections. We should really convert the value to an integer before we start putting it into the SQL string to avoid SQL injections. You can find more information on SQL injection from http://www.php.net/security.database.sql-injection.

In this example, because we are expecting the ID to be of type integer, we could use settype() function to prevent SQL injection.

if ($path_params[1] != null) {
settype($path_params[1], 'integer');
$query = "SELECT b.id, b.name, b.author, b.isbn FROM book as b WHERE b.id = $path_params[1]";
}

Here is the full PHP script with these updates in place.

<?php
$link = mysql_connect('localhost', 'sam', 'pass') or die('Could not connect: ' . mysql_error());
mysql_select_db('library') or die('Could not select database');
header("Content-Type: text/xml");
// Check for the path elements
$path = $_SERVER['PATH_INFO'];
if ($path != null) {
$path_params = spliti ("/", $path);
}
if ($_SERVER['REQUEST_METHOD'] == 'GET') {
if ($path_params[1] != null) {
settype($path_params[1], 'integer');
$query = "SELECT b.id, b.name, b.author, b.isbn FROM book as b WHERE b.id = $path_params[1]";
} else {
$query = "SELECT b.id, b.name, b.author, b.isbn FROM book as b";
}
$result = mysql_query($query)or die('Query failed: ' . mysql_error());
echo "<books>";
while ($line = mysql_fetch_array($result, MYSQL_ASSOC)) {
library system, implementingbook information, retrievingecho "<book>";
foreach ($line as $key => $col_value) {
echo "<$key>$col_value</$key>";
}
echo "</book>";
}
echo "</books>";
mysql_free_result($result);
}
mysql_close($link);
?>

Here is a sample output from this script when accessed with the Web browser using the URL http://localhost/rest/04/library/book.php/2.

Retrieve Operation

Create Operation

Again we will take the book resource to explain the implementation of create operation with HTTP POST verb. The URI mapping for the get books and create books is the same. Hence, we will have to use the same PHP script for implementing the create operation. The retrieve operation only needs us to specify a parameter, a book ID, to get a particular book, but to create a book we have to provide the data for the book as well. Hence note that the HTTP verb used is POST. So we need to check if we have received a POST request to get to know if we are supposed to insert data to the database.

if ($_SERVER['REQUEST_METHOD'] == 'POST') {

Once we have verified it is as a POST request, we have to pick the POST data and build an XML document out of that data.

$input = file_get_contents("php://input");
$xml = simplexml_load_string($input);

We assume the received input data to be of the following format:

<books>
<book><name>Book3</name><author>Auth3</author><isbn>ISBN0003</isbn></book>
<book><name>Book4</name><author>Auth4</author><isbn>ISBN0004</isbn></book>
</books>

Note that, rather than looking for a single book element, we have left the user the flexibility of creating multiple books, in other words, one or more books, with a single request.

Here is the PHP code that parses this XML document and inserts the data contained in the request to the database.

foreach ($xml->book as $book) {
$query = "INSERT INTO book (name, author, isbn) VALUES ('$book->name', '$book->author', '$book->isbn')";
$result = mysql_query($query) or die('Query failed: ' . mysql_error());
mysql_free_result($result);
}

For each book in the request payload, we insert the data into the database. Also, note that we do not expect the user to send an ID for the book while creating resource instances, rather the auto increment functionality of the database would be leveraged to generate an ID.

Here is the complete script with all retrieve and create logic in place.

<?php
library system, implementingservice PHP script$link = mysql_connect('localhost', 'sam', 'pass') or die('Could not connect: ' . mysql_error());
mysql_select_db('library')or die('Could not select database');
// Check for the path elements
$path = $_SERVER['PATH_INFO'];
if ($path != null) {
$path_params = spliti ("/", $path);
}
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$input = file_get_contents("php://input");
$xml = simplexml_load_string($input);
foreach ($xml->book as $book) {
$query = "INSERT INTO book (name, author, isbn) VALUES ('$book->name', '$book->author', '$book->isbn')";
$result = mysql_query($query) or die('Query failed: ' . mysql_error());
mysql_free_result($result);
}
} else if ($_SERVER['REQUEST_METHOD'] == 'GET') {
if ($path_params[1] != null) {
$query = "SELECT b.id, b.name, b.author, b.isbn FROM book as b WHERE b.id = $path_params[1]";
} else {
$query = "SELECT b.id, b.name, b.author, b.isbn FROM book as b";
}
$result = mysql_query($query) or die('Query failed: ' . mysql_error());
echo "<books>";
while ($line = mysql_fetch_array($result, MYSQL_ASSOC)) {
echo "<book>";
foreach ($line as$key => $col_value) {
echo "<$key>$col_value</$key>";
}
echo "</book>";
}
echo "</books>";
mysql_free_result($result);
}
mysql_close($link);
?>

It is the service PHP script that is provided here. As this chapter is focused on resource-oriented services, providing client code would deviate our focus. We will look into client scripts in the next chapter.

Next, let's see the script that implements the business operations that are related to the member resource. Since the concepts involved with retrieve, create and get member data are similar to those of book resource, let's look at the whole script together.

<?php
$link = mysql_connect('localhost', 'sam', 'pass') or die('Could not connect: ' . mysql_error());
mysql_select_db('library') or die('Could not select database');
$root_element_name = 'members';
$wrapper_element_name = 'member';
// Check for the path elements
$path = $_SERVER['PATH_INFO'];
if ($path != null) {
$path_params = spliti ("/", $path);
}
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
// Handle POST request. Insert the data posted to the database.
$input = file_get_contents("php://input");
$xml = simplexml_load_string($input);
foreach ($xml->memberas $member) {
$query = "INSERT INTO member (first_name, last_name) VALUES ('$member->first_name', '$member->last_name')";
$result = mysql_query($query) or die('Query failed: ' . mysql_error());
mysql_free_result($result);
}
} else if ($_SERVER['REQUEST_METHOD'] == 'GET') {
// Handle GET request. Return the member data or the list of members.
if ($path_params[1] != null) {
// Look for the given member
$query = "SELECT m.id, m.first_name, m.last_name FROM member as m WHERE m.id = $path_params[1]";
} else {
$query = "SELECT m.id, m.first_name, m.last_name FROM member as m";
}
$result = mysql_query($query) or die('Query failed: ' . mysql_error());
echo "<$root_element_name>";
while ($line = mysql_fetch_array($result, MYSQL_ASSOC)) {
echo "<$wrapper_element_name>";
foreach ($line as $key => $col_value) {
echo "<$key>$col_value</$key>";
}
echo "</$wrapper_element_name>";
}
library system, implementingservice PHP scriptecho "</$root_element_name>";
mysql_free_result($result);
}
mysql_close($link);
?>

You should be familiar with the concepts used in this PHP script already.

  • We check if the request is a POST request and create members

  • Else we check if the request is a GET request

    • If path information is present, we return the data for requested member

    • Else we return the list of members

Handling Multiple Path Parameters

The get member borrowings business operation would have the URI pattern /member/{member_id}/books. When mapped to the implementation, this will look something similar to:

A GET request sent to the above URL would return the list of borrowings by member with ID 1.

As we have already seen, we can use the following logic to pick the values in path information.

// Check for the path elements
$path = $_SERVER['PATH_INFO'];
if ($path != null) {
$path_params = spliti ("/", $path);
}

If the request URL is http://localhost/rest/04/library/member.php/1/books and you do a

print_r($path_params);

You will get the output:

Array
(
[0] =>
[1] => 1
[2] => books
)

We can use the following piece of logic to pick the path information and prepare the query to be executed accordingly.

if ($path_params[1] != null) {
if ($path_params[2] != null) {
if ($path_params[2] == 'books') {
// GET books borrowed by member
$query = "SELECT b.id, b.name, b.author, b.isbn, br.start_date, br.end_date FROM member as m, book as b, borrowing as br WHERE br.member_id = m.id AND br.book_id = b.id AND m.id = $path_params[1]";
$root_element_name = 'books';
$wrapper_element_name = 'book';
}
} else {
$query = "SELECT m.id, m.first_name, m.last_name FROM member as m WHERE m.id = $path_params[1]";
}
} else {
$query = 'SELECT m.id, m.first_name, m.last_name FROM member as m';
}

This will return an XML document similar to:

<books>
<book>
<id>2</id>
<name>Book2</name>
<author>Auth2</author>
<isbn>ISBN0002</isbn>
<start_date>2008-06-16</start_date>
<end_date></end_date>
</book>
</books>

If there are path parameters and if the third element is books, then we return the list of borrowed books by the member with the ID containing $path_params[1].This requires dealing with multiple nested IF-checks. An alternative approach would be to use a regular expression check where the path information is matched on a common term that we are looking for. As an example, this could be done in the following manner.

if (ereg("books", $path)) {
// GET books borrowed by member
$query = "SELECT b.id, b.name, b.author, b.isbn FROM member as m, book as b, borrowing as br WHERE br.member_id = m.id AND br.book_id = b.id AND m.id = $path_params[1]";
$root_element_name = 'books';
$wrapper_element_name = 'book';
}

Next, let's look into the borrow book business operation. The URI pattern for this operation is /member/{member_id}/books/{book_id}. When mapped to the implementation, this will look something similar to:

http://localhost/rest/04/library/member.php/1/books/2

With the above URL, if you do a

print_r($path_params);

You will get the output:

Array
(
[0] =>
[1] => 1
[2] => books
[3] => 2
)

When a POST request comes to this endpoint, what it means is that, member with ID 1 borrows book with ID 2. Here is the PHP code for looking into the parameters and creating a new book borrowing record.

if ($path_params[1] != null && $path_params[2] != null && $path_params[3] != null) {
if ($path_params[2] == 'books') {
// a book being borrowed by member
$today = date("Y-m-d");
$query = "INSERT INTO borrowing (member_id, book_id, start_date) VALUES ($path_params[1], $path_params[3], '$today')";
$result = mysql_query($query) or die('Query failed: ' . mysql_error());
mysql_free_result($result);
}
}

This code picks up the member ID and book ID from path information and creates a booking with start date set to today. Note that this would be inside the block where a POST request is processed.

The return book operation would be implemented for the same URL pattern, but with the HTTP method DELETE.

if ($_SERVER['REQUEST_METHOD'] == 'DELETE') {
// Handle POST request. Insert the data posted to the database.
if ($path_params[1] != null && $path_params[2] != null && $path_params[3] != null) {
if ($path_params[2] == 'books') {
// a book being borrowed by member
$today = date("Y-m-d");
echo $today;
$query = "Update borrowing as br SET end_date = '$today' where br.member_id = $path_params[1] and br.book_id = $path_params[3]";
echo $query;
$result = mysql_query($query) or die('Query failed: ' . mysql_error());
mysql_free_result($result);
}
}
}

This code picks up the member ID and book ID from path information and updates the booking with end date set to today.

Here is the complete source code for member.php script, where all of following business operations are included:

  • List members

  • Create member(s)

  • Get member data

  • List borrowings

  • Borrow book

  • Return book

<?php
function init_database() {
$link = mysql_connect('localhost', 'sam', 'pass') or die('Could not connect: ' . mysql_error());
mysql_select_db('library') or die('Could not select database');
return $link;
}
function handle_borrow_book($member_id, $book_id) {
$today = date("Y-m-d");
$query = "INSERT INTO borrowing (member_id, book_id, start_date) VALUES ($member_id, $book_id, '$today')";
$result = mysql_query($query) or die('Query failed: ' . mysql_error());
mysql_free_result($result);
}
function add_member() {
$input = file_get_contents("php://input");
$xml = simplexml_load_string($input);
foreach ($xml->member as $member) {
$query = "INSERT INTO member (first_name, last_name) VALUES ('$member->first_name', '$member->last_name')";
$result = mysql_query($query) or die('Query failed: ' . mysql_error());
mysql_free_result($result);
}
}
function print_result($query, $root_element_name, $wrapper_element_name) {
$result = mysql_query($query) or die('Query failed: ' . mysql_error());
echo "<$root_element_name>";
while ($line = mysql_fetch_array($result, MYSQL_ASSOC)) {
echo "<$wrapper_element_name>";
foreach ($line as $key => $col_value) {
echo "<$key>$col_value</$key>";
}
echo "</$wrapper_element_name>";
}
echo "</$root_element_name>";
mysql_free_result($result);
}
library system, implementingpath parameters, handlingfunction get_books_borrowed($member_id) {
$query = "SELECT b.id, b.name, b.author, b.isbn, br.start_date, br.end_date FROM member as m, book as b, borrowing as br WHERE br.member_id = m.id AND br.book_id = b.id AND m.id = $member_id AND br.end_date is NULL";
$root_element_name = 'books';
$wrapper_element_name = 'book';
print_result($query, $root_element_name, $wrapper_element_name);
}
function get_member($member_id) {
$query = "SELECT m.id, m.first_name, m.last_name FROM member as m WHERE m.id = $member_id";
$root_element_name = 'members';
$wrapper_element_name = 'member';
print_result($query, $root_element_name, $wrapper_element_name);
}
function get_members() {
$query = "SELECT m.id, m.first_name, m.last_name FROM member as m";
$root_element_name = 'members';
$wrapper_element_name = 'member';
print_result($query, $root_element_name, $wrapper_element_name);
}
function handle_return_book($member_id, $book_id) {
$today = date("Y-m-d");
$query = "Update borrowing as br SET end_date = '$today' where br.member_id = $member_id and br.book_id = $book_id";
$result = mysql_query($query) or die('Query failed: ' . mysql_error());
mysql_free_result($result);
}
$database = init_database();
// Set the content type to text/xml
header("Content-Type: text/xml");
// Check for the path elements
$path = $_SERVER[PATH_INFO];
if ($path != null) {
$path_params = spliti("/", $path);
}
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
// Handle POST request. Insert the data posted to the database.
if ($path_params[1] != null && $path_params[2] != null && $path_params[3] != null) {
if ($path_params[2] == 'books') {
// a book being borrowed by member
handle_borrow_book($path_params[1], $path_params[3]);
}
} else {
add_member();
}
} else
if ($_SERVER['REQUEST_METHOD'] == 'GET') {
// Handle GET request. Return the list of members.
if ($path_params[1] != null) {
if ($path_params[2] != null) {
if ($path_params[2] == 'books') {
// GET books borrowed by member
get_books_borrowed($path_params[1]);
}
} else {
// GET member details for given ID
get_member($path_params[1]);
}
} else {
// GET all members
get_members();
}
} else
if ($_SERVER['REQUEST_METHOD'] == 'DELETE') {
// Handle DELETE request. Handle the book return operation.
if ($path_params[1] != null && $path_params[2] != null && $path_params[3] != null) {
if ($path_params[2] == 'books') {
// a book being returned by member
handle_return_book($path_params[1], $path_params[3]);
}
}
}
mysql_close($database);
library system, implementingpath parameters, handling?>

Summary

This chapter covered the steps that you would have to follow in designing and implementing a resource-oriented service, in detail. Identifying resources and business operations for a given problem statement, designing the URI patterns, selecting the correct HTTP verbs, mapping URI and HTTP verbs to business operations were covered using the library example. Implementing the services and business operations using PHP was explained in detail, step by step.

In the next chapter, we will cover how to implement resource-oriented clients using PHP for the library example introduced in this chapter.

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

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