Tutorial - Model, Collection and Request handler
This tutorial will show you to create a Web Service using LudoDB
- Create LudoDB models for cities, states and countries.
- Create LudoDB collections for cities, states and countries.
- Make the countries collection available as a Web Service.
- Create a simple front-end controller(index.php) which outputs the countries collection.
At the end, we will be able to get countries, states and cities in this JSON tree structure when opening index.php:
When viewed with LudoJS this JSON can be used to populate a tree like displayed in countries.php
You need access to a WebServer with PHP 5.3 or newer.
Create root folder for the demo.
Create a new folder called cities.
Download the LudoDB framework from GitHub or clone it using git (command line from the cities folder):
LudoDB should be exracted into cities/ludoDB.
Autoload builder (Optional)
If you have the PHP Autoload builder available at https://github.com/theseer/Autoload installed, it will make your work much easier. It will scan your directories and create one single autoload.php file for you. Then you only need one require/include statement in your code instead of one for each of the PHP files you'll need to include.
If you're having problems installing Autoload from the command line using PEAR install, try this code:
pear install -a -f pear.netpirates.net/Autoload
A LudoDBModel class represents a database table. An instance of a LudoDBModel class represents a row in the database table.
To create a new LudoDBModel, we use this code
Now, we need to configure the database table, i.e. the columns. This can be done directly in the class by specifying a
where the $config property specifies how the database table looks like.
You can also specify the configuration in a JSON file. To do that, you simply specify:
And add a file called Country.json inside a sub folder named JSONConfig. The JSON should be a JSON encoded version of the PHP $config array.
Now, let's configure the Country table. The first we specify is name of database table. Let's call it "country":
The next property is "sql". "sql" is an optional property which specifies the sql used when you want to look up a record. If you choose not to define "sql", LudoDB will create one on the fly based on table name and columns.
For code clarity, it's preferable to always specify "sql". For Country, we will set sql to:
The question marks in the sql definition is a placeholder for the arguments sent to the constructor when a new Country instance is created.
Now, let's move on to the columns which are defined inside a "columns" array. The key of the array is the name of the column. The value of each key may be a string specifying the definition of the column or an array containing column definition and other properties.
The config of the id field, we set to "int auto_increment not null primary key":
For the "name", attribute we use an array. We set "db" to "varchar(255)" and we also specify an access attribute to "rw". The access attribute specifies access to the column for the LudoDBModel::save and LudoDBModel::read methods. "r" means read access and "w" write access. The default value is "", i.e. no access. The exception is the "id" field which has "rw" as default.
You will still able to write and read from column internally in your class.
Our Country class should now look like this:
In the LudoDB model, we can also define default data which are inserted when the table is created by the LudoDBModel::createTable() method.
Let's add "Norway", "United States" and "Germany" as default data for country inside the "data" property.
And that's it for the Country class.
Now, let's move on to the State class. In our example, Texas in the United States, Rogaland in Norway and Bavaria in Germany, all represents states.
For the State class will specify a config where "state" is the name of the database table. It should contain the columns id, name and country, where country is a reference to the id of the country table.
The definition of id and name is the same for State as it was for Country. The last column "country" is an int referencing the id of the country table. We define this using the "references" attribute:
The value of the references attribute is the same as you would use in a MySQL create table statement. Here, the column config is an int references the id of the country table. And when the country is deleted, all states with reference to that country should also be deleted(i.e. on delete cascade).
The last thing we do in the State class is to define indexes and set default data. indexes is a config property which should be an array with the names of the columns which should be indexed. For this table, "country" should be indexed.
This gives us this State class:
The last Class to create is City. The configuration of this table is the same as for State except that it has a state column referencing state(id) instead of a country column:
That's it for the LudoDBModel classes. You should now have three files inside your "cities" folder, Country.php, State.php and City.php.
The LudoDBCollection classes
LudoDBCollection classes are used to retrieve a collection of records, example: all states of a country. You create a new collection class by extending LudoDBCollection.
The LudoDBCollection class is also configured using the $config property or by JSON.
Let's start bulding the Cities collection(Cities.php). The sql should be
You now have the simplest form of a working LudoDBCollection class.
One thing we want to specify in the config is "model". "model" is a name of a LudoDBModel class. By specifying "model", LudoDBCollection will call the getValues method of the model for each row in the result set. This is useful when you want to avoid returning values of read-only columns.
The name of the model for Cities is "City".
And that's all we need for the Cities class.
The sql and model config of States are about the same as for Cities:
but we're not quite done with the States collection yet. What we want to do is to merge in the cities of this state. We do that using the "merge" config attribute. "merge" is an array containing three properties: "class", "fk" and "pk".
"class" is a name of a LudoDBCollection class, "fk" is the name of foreign key column and pk is the name of the column the foreign key is referencing.
For States, we set class to "Cities". We set "fk" to "state" since that's the name of the column in the city table where reference to state is stored. We set "pk" to "id" since that's the name of the column the "state" column in "City" is referencing.
This gives us this code:
We're almost done with the States collection now. The last thing we want to add to the config is "childKey" and "hideForeignKeys".
childKey is a string which will be used as array key for the merged collection. By setting "childKey" to "cities", the Cities collection will be returned as a "cities" array("cities" => array()).
ps! childKey can be defined globally on the config object for all merged collections, or inside the "merge" array, i.e. with "fk", "pk" and "class".
"hideForeignKeys" is a boolean property which, when set to true will hide foreign keys in the merged collection, i.e. state property of city will not be shown.
The final code for the States class now looks like this:
The last collection we want to create is Countries. The configuration of this collection is the same as for States:
For now, this completes our LudoDBModel and LudoDBCollection classes. We have created 3 models and 3 collections. Since we are merging States into Countries and Citites into States, we will get both States and Cities when calling the Countries::read method.
Now, let's move on to index.php, our front end controller.
In index.php we want to create a LudoDBRequestHandler instance and use it to output the JSON for the Countries collection.
The code for the request handler looks like this:
We create a LudoDBRequestHandler instance and call the handle method, passing the request we want to have processed.
The argument to the handle method is in a web service format where tokens are separated by a slash(/).
The first token is the name of the class or resource which the request should be delegated to. The lsat token("read") is the name of the service or more specific the name of the method which should handle the request.
Any arguments in between the first and last are passed as constructor arguments to the resource, example: request: City/1/read will give you the data for City with id equals 1.
The LudoDBService interface
Resources handled by the LudoDBRequestHandler class has to implement the LudoDBService interface. The interface contains the following methods which has to be implemented.
- validateArguments is used to validate arguments sent to the constructor of the resource class. When invalid, you may return false or throw a LudoDBException exception. The name of the service and an array containing constructor parameters are passed to this method.
- validateServiceData is used to validate eventual POST data($_POST['data']) which will be passed to the service method. As for validateArguments, you can return false when invalid or throw a LudoDBException if you want to return an error message.
- shouldCache should return a boolean when the LudoDBRequest handler should try to get value from the ludo_db_cache database table instead of passing the request to the resource. This is useful if a service requires a lot of database queries to complete.
- getValidServices should return an array with the name of valid services, example return array("read");
- getOnSuccessMessageFor return default success messages for successful requests. Example: return "Data saved successfully";
We need to implement these methods for our Countries collection. So re-open Countries.php and add the following code:
Only the read service is supported, so we return "read" in getValidServices. The "read" method is already implemented in LudoDBCollection so we don't need to create a new "read" method inside our Countries class.
validateArguments should return true only when number of arguments is 0 since our service does not require an arguments. The read service does not support any data either, so we return true from validateServiceData only when $data is empty.
Finally, we return true from cacheEnabled and an empty string from getOnSuccessMessageFor. For LudoDBModels and LudoDBCollection classes, we can choose to not implement the getOnSuccessMessageFor method because it has it's default implementation in LudoDBObject which is parent class of LudoDBModel and LudoDBCollection.
That's it. Countries is now a LudoDBService class. Let's move back to index.php. This is the code we have so far:
If you open index.php in your browser, you will see a blank screen or an error message. We need to include our PHP classes. Files can be included manually, or we can create an autoload.php file using the Autoload php PEAR plugin available at https://github.com/theseer/Autoload.
Use this code at the top of index.php to manually import the required php files:
Import using theseer/Autoload:
Open a command line/shell and go to the citites folder. There type:
Now, you only have to include autoload.php in index.php:
Specify database connection details.
The next thing we need to do inside index.php is to specify the database connection details.
Replace the values above with the connection details for your database.
Create database tables if not exists
For this demo, we want to make sure that the database tables are created. Usually, you will not have such code in your front end controller. So bear in mind this is for the demo only.
This is the code used to create database tables when they doesn't exists:
Finally, I call LudoDB::enableLogging(); to see number of queries and time used to process the requests in the response from LudoDBRequestHandler.
The final code for index.php now looks like this:
If you open http://your-domain/path/to/citites/index.php, you should now get JSON for the Countries collection. To get a pretty view, open the page in Chrome or Firefox after installing a JSONView addon/extension.
We have seen how to create LudoDBModel and LudoDBCollection classes. We have also taken a quick look at the LudoDBRequestHandler class.
The code for this tutorial is also available inside the examples folder of the LudoDB repository(or zip). I will also suggest looking at the mod_rewrite code inside examples/mod_rewrite. Instead of a static index.php it has a dynamic router.php and a .htaccess file which parses url's and passes them to router.php. By opening the url http://yourdomian/path/to/mod_rewrite/Book/1/read in your browser. "Book/1/read" will be passed to Router.php which will pass the request to a LudoDBRequeestHandler instance and output the result.
Use with LudoJS