+======================================================================+ ___ ___ ___ ___ /\ \ /\ \ /\ \ /\ \ /::\ \ /::\ \ /::\ \ /::\ \ /:/\:\ \ /:/\ \ \ /:/\:\ \ /:/\:\ \ /::\~\:\ \ _\:\~\ \ \ /::\~\:\ \ /::\~\:\ \ /:/\:\ \:\__\ /\ \:\ \ \__\ /:/\:\ \:\__\ /:/\:\ \:\__\ \/__\:\/:/ / \:\ \:\ \/__/ \/_|::\/:/ / \/__\:\/:/ / \::/ / \:\ \:\__\ |:|::/ / \::/ / /:/ / \:\/:/ / |:|\/__/ /:/ / /:/ / \::/ / |:| | /:/ / \/__/ \/__/ \|__| \/__/ ~ A Simple RESTful API ~ @author @version 1.3.1 @copyright 2008 TypeOneError Studios @url http://typeoneerror.com/asra @todo: _ docs for performing validations on data for saving +======================================================================+ INTRODUCTION The ASRA package is a quick way of creating an API for exporting results from a MySQL database for flash or perhaps another application. Through the use of pretty urls, you can create a decent export system that automatically handles xml, json, serialized, yaml (experimental), or vars output. Views can also be cached based on the urls. See the CACHING section for more info on how caching works. See the DUMPING section for more information of content export types. From the root of the API folder, urls basically work like so: /type/controller/method/param An example URL to export a photo by id as json then might be: /json/photos/id/1 Content is exported/imported through the use of controllers. The system itself is similar to the MVC pattern. That is if you think of how you want the output to display (XML? JSON? YAML?) to be the "V" and the custom queries to be the "M" ;). No, there is no database abstraction here. Merely a quick way to export data from a MySQL database in many different formats by only writing a few simple queries (and sometimes none at all!). SETUP Basically, the files under /app are your files. You should edit /app/config.ini with your database settings and then create controllers (the classes that handle the rewritten URLs and export content). After you've edited your config file and pointed it to the right database, you can automatically export any database data by using the database name as your url. /portfolio_images This would dump a table called `portfolio_images` if it exists (in order to use this functionality you'll need to set REQUIRE_CONTROLLER to false - this may present a security risk for tables like "users" with passwords, so it is set to true by default). See the DUMPING section for more information of content export types. If you want to define more complex systems for exporting data, you should create controllers. You can check out the files into a subdirectory of your webroot. I usually put them in a folder called "asra" or "api". Then - if you are working locally, the path to the ASRA installation will be something like http://localhost/api/type/controller/action/param Continuing the example URL for a single photo by id above: http://localhost/api/json/photos/id/1 Say all of a sudden your Flash developer decides he wants the data as XML instead of JSON. He can easily point his code to: http://localhost/api/xml/photos/id/1 Same data, different mime-type, no coding necessary to change formats. THE CONFIG FILE In the config file in /app/config.ini, you should define the settings for connecting to your mysql database. You can make as many different connection vars as you like. The ASRA config file is meant to be useful to the Zend_Config_Ini class in the Zend Framework but is set up by default to use the file in /app/config.ini. 1) Start by defining your default connection params (I use the exact same parameter names as Zend Framework so we can integrate easily). The default for ASRA is typically required: [default] database.adapter = mysqli database.params.host = localhost database.params.dbname = database database.params.username = root database.params.password = root 2) You can "extend" the default by using the same ":" (colon) syntax as Zend. The difference with ASRA is that the extended name is a URL where the API is deployed. So, if you needed a different username and password on "sampledomain.com" than in your local enviroment you might try something like: [default : sampledomain.com] database.params.username = user database.params.password = password 3) So, when the ini is parsed on sampledomain.com it will inherit all the default parameters but will use the username and password defined above instead. 4) If you wanted to use a different config location, set the CONFIG_FILE directive in /index.php. By default it points to /app/config.ini, but say you want to use the same ini file in your Zend app - you might change it to point to ../application/config/config.ini (or the relative path to where you are keeping your Zend ini files). SETTINGS There are a number of settings you can change in /api/index.php. This is the file that initializes the bootstrap file and contains a series of constants that control things like where the api library is, whether to use caching or not, etc. Follow the comments in that file for instructions on what the constants do and whether it's a good idea to edit them. You could probably get away with not editing this at all but if you need to change something or add something, here are what I feel are the most important settings in there (and the ones that I've changed frequently): CACHE_OUTPUT If this is set to true, views are cached where the method `setCacheLifeTime` is called in a controller action. Check the CACHING section for more on that. CONFIG_FILE Path to the configuration config.ini file. See THE CONFIG FILE section on more information about using or changing the configuration paths. FULL_ERROR_REPORTING Set this to true if you want to see all PHP errors during development. This is set to false by default and it's highly recommended that you turn off all error reporting prior to moving the API to production. LOG_QUERIES If you turn on this setting, whenever a SQL query is run, it gets written as a new line in /log/trace.log (this path needs to be writable). Probably not the greatest idea to leave this on in your production environment. REQUIRE_CONTROLLER This is set to true by default. Basically, this means that you must create a controller to access the database. If you set this to false, you can automatically create a database export in your format determined by the URL. This of course could be an issue in a `users` table w/ passwords. Hence, it's set to false. CONTROLLERS To make controllers for your URLs, create a file in /app/controllers. Controllers must extend the Controller class. Any methods you define in a controller can be accessed by going to the path of the same name. Controller file names should reflect the database table you are modeling and the class name should be a camelized version of the same. So, for methods related to the `portfolio_types` database, we might create: /app/controllers/portfolio_types.php Which after extending Controller and camelizing the class name: class PortfolioTypes extends Controller { public function index() { $this->dump(); } public function type($id) { $this->single($id); } } This would be accessed at /portfolio_types. The controller URL automatically looks for an "index" method, so it will run the code automatically if you define it (in this case it just dumps the table data). /portfolio_types/type/1 would fetch a single portfolio type by the ID of 1. An example controller is provided in the ASRA package. RESERVED CONTROLLER NAMES You cannot at this time use controller names that are the same as an export type name because the export type is not required. Reserved names: 'debug', 'json', 'serial', 'vars', 'xml', and 'yaml' API METHODS AND PROPERTIES Here are the public methods of the API that are available to use in a Controller subclass: array clean ( array $row ); The clean method loops through an array of results and runs some formatting functions to ready the output for flash. It is mostly used internally and really only handles single dimension arrays (like a returned mysql result). @param array $row the array to clean void clearCache ( [string $path = null] ); Clear a cached view. If the $path is null, then the cached cleared is the current view's. @param string $path [optional] path of cached file to delete in a url format e.g. $this->clearCache('/xml/controller/action/param') would clear the cached file called "controller.action.param.xml" void clearCaches ( void ); Clears all cached files in the application (deletes them) void deleteCache ( string $path ); Delete a cached file by name. @param string $path the file to delete. e.g. would be something like $this->deleteCache("controller.action.param.xml"); void dump ( void ); Outputs the table being used by the controller. If you want to use a table that is not named the same as the class, you can set the $uses property in your subclass. void error ( string $errorMsg, string $errorCode ); Publishes an error in the format of your choosing (determined by the url string). Some error code constants are available in /core/lang.php. @param string $errorMsg message to display @param string $errorCode defined in /core/lang.php void find ( [mixed $params = null] ) You can use a shorter version of queries by passing the parameters as an array instead. The find method uses API::query to printOut results, so it's the same thing, just called differenly. You can pass as an array or as a string. Here's how and some examples: @param array $params An associative array of parameters. Possible params are: "conditions" : an array of select conditions "fields" : an array of fields to select "limit" : for LIMIT query as string "order" : how to ORDER BY as string "subquery" : see "@param mixed $subquery" in the "API::query" method e.g. if you were in a controller for the table `users`: $this->find(array( 'conditions' => array('active' => 1), 'fields' => array('username'), 'order' => 'username DESC' )); would be the same as calling: $this->query("SELECT `username` FROM `users` WHERE active = 1 ORDER BY username DESC"); @param string $params You can pass the conditions as a string as well: $this->find('active = 1'); would be the same as calling: $this->query("SELECT * FROM `users` WHERE active = 1"); string getCachePath ( string $path ) Returns the file path of a cache or possible cache path based on the ASRA URL. e.g. The following would return the string "sample_table.id.1.xml" $this->getCachePath("/xml/sample_table/id/1/"); string getExportType ( void ); returns the export type as a string (serial|vars|xml|json|xml) int id ( void ); returns the last inserted mysql id (same as mysql_insert_id) bool isGet ( void ); returns true if request method is GET bool isPost ( void ); returns true if request method is POST void log ( string $output ); Writes a string to /log/trace.log on a new line. When LOG_QUERIES is set to true, this is used to write the queries. However, you can directly access this method for debugging. Should probably not be used in production level applications. @param string $output the string to write to the trace log void printOut ( array $results ); printOut is used by query, error and other internal functions to publish the data passed in the format that is determined by the requesting url. You can access it directly to publish results. @param array $results an array to display in the requested format e.g. $this->printOut(array('valid' => 0, 'message' => 'POST is required')); array run ( string $query [, mixed $subquery ] ); Function used by query function to get results. This just results the results instead of printing them out. See the docs below under the `query` method for the parameter explanations. void save ( array $data [, string $table = null, bool $output = false] ); Saves an array of data to either the table specified by $uses setting or to the non camelized version of the class name. @param array $data The data should be an associated array keyed by the fields in the database. If you specify an `id` key, it will attempt to update the row with that id. See the SAVING DATA entry for more info. @param string $table [optional] Name of the table to write to. If this is not set, then the controller looks to the $uses property for the table. @param bool $output [optional] Whether to `printOut` the result of the insert void setCacheLifeTime( [mixed $seconds = false] ); if seconds is an integer, this method sets the cache lifetime for the current view. @param mixed $seconds [optional] void setExportType ( string $type ); sets the export type @param string $type (serial|vars|xml|json|xml|yaml) void single ( int $id ); Outputs a single result from the table being used by the controller. @param int $id ID of the row you wish to export. void query ( string $query [, mixed $subquery] ); Queries the database and outputs the results. This is one of the more commonly used methods in ASRA. @param string $query The query to run to get results. @param mixed $subquery Subqueries use vsprintf to format a second query for (perhaps) returning more results related to the original query. The subquery can either be a string or an array. If it's an array you can use the following keys to effect the result: string $query // -- subquery to run, required [string $key] // -- what to name the results, defaults to 'subquery' [string $field] // -- the field to use from the first query's result // -- for formatting the second query To see some examples of this parameter, see the section entitled USING SUBQUERY EXAMPLE. bool validates ( void ); Tests the validation properties to against their settings. This is a lot like the CakePHP validator. See the SAVING DATA entry for more info. Here are the properties you can get/set in a Controller subclass and what they do. Each are either protected or public. As the Controller class is an abstract class, you cannot directly instantiate it and have to access these variables via your subclass. public array $data The data array contains any POST vars sent to the URL in an array. See the SAVING DATA section for more details. public array $errors Store validation errors while looping and checking valid keys. See the SAVING DATA section for more details. protected string $uses The table that the controller users for generic results. This includes the table which methods like dump and single use. class PortfolioTypes extends Controller { // -- maybe the database isn't named very well // -- and you want it to be cleaner in the URL protected $uses = 'ptypes'; } protected array $validate **TODO - Documentation Needed** public array $varsGet Any GET variables get pushed here public array $varsPost Any POST variables get pushed here And finally, here are some useful static methods for use anywhere in your application. These are all derived from the static classes located in /api/lib/utils and there are more in there for you to use. These are the two most commonly used ones: Arrays::print_array ( array $array ); Like print_r but wrapped in
 tags. You can also use the 
		shorthand method Arrays::pr
		
	Strings::makesafe ( string $string );
		Use to prevent sql injection. USE IT.
	

DUMPING

By default, ASRA exports content in xml. Here's the currently supported export formats:

XML            // -- key:xml
JSON           // -- key:json
SERIALIZED     // -- key:serial
VARS           // -- key:vars
YAML           // -- key:yaml     (experimental only)

To toggle between the different outputs, prepend your url with its related key. For
example,
	
	/json/portfolio_types/type/1 	// -- exports json
	/xml/portfolio_types/type/1     // -- exports xml
	/vars/portfolio_types/type/1	// -- exports var=value& pairs
	/yaml/portfolio_types/type/1	// -- exports yaml 



CACHING

The API can create caches of your views, speeding up the retrieval of data significantly
and reducing the burden of re-querying mysql for every call. To use caching you simply
set a cache lifetime inside a controller action:

	class Portfolio extends Controller {
		public function index() {
			$this->setCacheLifeTime(3600);
			$this->query("SELECT * FROM portfolio");
		}
	}
	
Caching is by second, so this would create a cache every hour. Each cache is also
type dependent, so if you go to /xml/portfolio, it will create a cache called
portfolio.index.xml and if you go to /api/json/portfolio, it will create a cache
called portfolio.index.json. 

To clear all caches, you can use the `clearCaches` method which deletes all the 
cached files. To clear a single cache, you can call the `clearCache` method or
the `deleteCache` method.


DEBUGGING

Turning on debugging forces the results to be printed out using 
 which
sometimes makes reading certain format easier. To turn it on, prepend your url with
"debug." This comes before the dumping type definition. So, these urls turn on
debugging:

	/debug/json/portfolio_types/type/1      // -- exports json with debug
	/debug/xml/portfolio_types/type/1       // -- same with xml
	

LOGGING

You can log queries performed by setting LOG_QUERIES to true in the index.php file. This
defaults to false. If your /log folder is writable, the logger will write the queries on
a line-by-line basis to /log/trace.log. If you want to pass custom log data to the file, 
you just have to call the log method of the api in your controller:

	class Test extends Controller {
		public function index() {
			$this->log("Hello World!");	// "Hello World!" appears in trace.log
		}
	}
	

USING SUBQUERY EXAMPLE (A BASIC ASRA EXAMPLE)

Here is an example of how the $subquery param of the api's query function works. We'll
start with a simple example and select XML as our export (for demonstration). You have
two tables, one called `portfolio` and the other called `portfolio_images.` For example's
sake, let's say portfolio_images stores the images for each portfolio item. So, we'd want
our results to also contain a list of images. To start we might create our controller:

	class Portfolio extends Controller {
	}

And then define our index function and perform a simple query.

	class Portfolio extends Controller {
		public function index() {
			$this->query("SELECT * FROM portfolio");
		}
	}

This might export something like:

	
		
			1
			My Piece Title
		
	

If we wanted to get the portfolio images we might try:

	class Portfolio extends Controller {
		public function index() {
			$this->query(
				"SELECT * FROM portfolio",
				"SELECT * FROM portfolio_images WHERE portfolio_id = %s"
			);
		}
	}

Now we get:

	
		
			1
			My Piece Title
			
				
					1
					image1.jpg
				
			
		
	

The $subquery would use the results from $query and apply the subquery using the id for
each row of $query. If you use a '%s' for formatting in your subquery, it will be
replaced by the `id` field of the previous query. To use a different field, set the
`field` key of the $subquery parameter to the field you'd wish to insert.

The results would be selected into a group called 'subquery.' Then...

	class Portfolio extends Controller {
		public function index() {
			$this->query("SELECT * FROM portfolio", array(
				'query'  => "SELECT * FROM portfolio_images WHERE portfolio_id = %s",
				'key'    => 'images'
			));
		}
	}

Now my results are selected into a sub group called images:

	
		
			1
			My Piece Title
			
				
					1
					image1.jpg
				
			
		
	
	
If I wanted to change the name of the subquery and use the `portfolio_category` field
from the intial results, I could try:

		class Portfolio extends Controller {
			public function index() {
				$this->query("SELECT * FROM portfolio", array(
					'query' => "SELECT * FROM portfolio_images WHERE category = %s",
					'key'   => 'category_images',
					'field' => 'portfolio_category'
				);
			}
		}

And so on...


SAVING DATA

ASRA also has some basic functionality for saving/inserting rows to a database. Of course, you could
use Controller::run() or Controller::query(), but Controller::save() provides easier saving with
no queries necessary. First you could check that data has been sent and that it's a POST request (
obviously you'd want to also add some security checks when accepting post data - not covered here):

class Comments extends Controller {
	public function add() {
		// -- data is set when posting to ASRA
		// -- isPost() makes sure it's a POST request
		// -- post vars are also stored in $this->varsPost;
		if ($this->data && $this->isPost()) {
			// -- save the data
			$this->save(array(
				'comment'  => $this->data['comment'],
				'user_id'  => $_SESSION['user_id'],
			), 'comments', false);
		} else {
			// -- printOut a custom error message
			$this->printOut(array('valid' => 0, 'message' => 'POST is required'));
		}
	}
}

This would insert a record into comments. The Controller::save() function's signature looks like this:

	void save($data, $table = null, $output = true);
	
If table is null it looks next for the protected $uses parameter for which database to insert. $output
is used to set whether to export the resulting query (in the same manner as the normal asra export) 
info or just before the query.


VALIDATING DATA FOR SAVE

**TODO - Documentation Needed**