- The Basics
- Setup
- Configuration
- Configuring ASRA as an External
- Settings
- Controllers
- Reserved Controller Names
- Dumping
- Debugging
- Logging
- Querying
- Saving Data
- Validation
- URL Formatting
The Basics
The ASRA package is a quick way of creating an mini API for exporting results from a MySQL database for use in Flash, Flex or perhaps another application. In an ASRA api, you extend and create controllers that contain methods that query the database and export results. How the results are exported can be changed by changing the path in the url. If your webroot is pointing to the ASRA folder (/trunk in the repository), URLs basically function with the following syntax:
/type/controller/method/param
In the above structure, type is how you want to display your results (the view). At this point, you can export json, xml, var=value pairs, serialized arrays, and yaml. The controller part is the name of a custom controller you create to handle the calls to database. Method is the function you want to call in the controller which actually has some "asra code" in it. Afterwards, you can pass as many params to the method (/param1/param2/param3 would go to function method($param1, $param2, $param3)). Using this structure, an example URL to export a photo by id as json then might be:
/json/photos/id/1
In this case, we want to export by "json"; we want to call the "id" method of "photos" and pass it a parameter of "1" ... of course, the actual code must be defined to handle those params, but there are many functions pre-built in ASRA to handle that.
Setup
First grab the latest codebase from the trunk of the repository. You can find the links and instructions on the download page. There is not currently a file download package at this time, but feel free to contact me if you want to check it out.
You can check out the files into a subdirectory of your webroot. I usually put them in
a folder called "asra" or "api" (your best bet would be to go to the webroot of your site and svn export http://svn.typeoneerrordev.com/asra/trunk 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 with an 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.
Anyway, from the root of the asra trunk, your files will go under /app and asra's library is all primarily in /core. You'll start by editing /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 the root key of the URL:
/api/portfolio_images
This would dump the rows in 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.
The Config File
In the configuration 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 environment 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. This will also carry over to www.sampledomain as www is enabled by default. Other subdomains will not though - you'd have to explicitly define dev.sampledomain.com...may be different in future releases.
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 /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. If you experience a time when navigate to a URL and it's totally blank, it's probably because of a run-time error. Switch this to "true" to find it.
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
The main vein of development in an ASRA api is creating controllers. Controllers handle changes in the URL and export content accordingly.
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 first letter uppercased and camelized (each word is capitalized and spaces and other extraneous characters are removed) 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. See the demo tutorial for a more thorough controller sample and the subqueries section has a decent tutorial for building one from scratch.
Reserved Controller Names
You cannot at this time (well you might be able to, but you shouldn't!) 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'
Dumping
By default, ASRA exports content in xml. Here are the currently supported export formats and the keys needed to use in the URL to activate them as the export type:
XML - key:xml
JSON - key:json
SERIALIZED - key:serial
VARS - key:vars
YAML - key:yaml
To toggle between the different outputs, prefix 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
XML, JSON, and YAML are all data-interchange formats and are pretty standard (yaml isn't used a whole lot yet). Serialization is a way of encoding data for transmission. In this case, it is serialized and URLencoded so it makes it easy to load into an application such as Flash (you'd just need to unencode it on the other end). Var/Value pairs is just a list of values assigned to parameters and separated by ampersands. This was a pretty standard way of loading content in the AS1 days and is here for legacy or simple support. An example follows:
result0_fname=Ben&result0_lname=Borowski&result1_fname=Miles&result1_lname=Biles
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 (no api since that's the root of the asra api in this case).
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. These can be accessed through a Controller subclass but are part of the API class.
Debugging
Turning on debugging forces the results to be printed out using <pre></pre> 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:
// -- export json with debugging
/debug/json/portfolio_types/type/1
// -- same with xml
/debug/xml/portfolio_types/type/1
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
}
}
Querying and Subqueries
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:
// -- create in /app/controllers/portfolio.php
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");
// -- we could also use $this->find();
}
}
Browsing to our development URL at http://localhost/portfolio OR http://localhost/xml/portfolio...this might export something like:
<rsp>
<item>
<id>1</id>
<title>My Piece Title</title>
</item>
</rsp>
If we wanted to get the associated 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:
<rsp>
<item>
<id>1</id>
<title>My Piece Title</title>
<subquery>
<item>
<id>1</id>
<source>image1.jpg</source>
</item>
</subquery>
</item>
</rsp>
The $subquery would use the results from the first $query and apply them to the subquery using the id for
each row. 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:
<rsp>
<item>
<id>1</id>
<title>My Piece Title</title>
<images>
<item>
<id>1</id>
<source>image1.jpg</source>
</item>
</images>
</item>
</rsp>
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...
There are other functions for querying the database such as find, single, run, save and more. Check out the API Reference for the API class and Controller class to explore them.
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 an array of fields/values to "comments"
// -- the false indicates that we don't want to
// -- echo the save status response out
$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( array $data [, string $table = null, bool $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**
URL Formatting
Your URLs can contain a set of delimiters for separating words. The basic allowed set is _ (underscores), - (dashes), and . (periods). Let's for example's sake say you have a table called `swatch_samples.` You would create a controller called SwatchSamples first. Then you could access its methods from the following (underscores are preferred):
/swatch_samples
/swatch.samples
/swatch-samples
Furthermore, if you created a function in SwatchSample called swatchDetailView it could be accessed via the following URLs:
/swatch_samples/swatchDetailView/1
/swatch-samples/swatch-detail-view/1
/swatch.samples/swatch.detail.view/1
Again, it's up to you, but the delimited versions are preferred over the full function name. PHP is case insensitive so /Swatch-Samples/SwatchDetailView should work too.
Also, clear naming is a good idea as well. The above example is pretty wordy. Maybe instead of using the table name, you would just set the $uses parameter and make more friendly URLs:
class Swatches extends Controller {
protected $uses = 'sample_swatches';
public function detail($id) {
$this->single($id);
}
}
The following is less typing and much more clear. So while you can be explicit with naming, sometimes it's less advantageous to do so.
/swatches/detail/1