Stack.PHP is a PHP library that acts as a wrapper over the Stack Exchange API. The library is designed to be compatible with all versions of PHP >= 5.2.
This document assumes a basic knowledge of PHP syntax and viewing PHP files in a web browser. A passive knowledge of the Stack Exchange API is not necessary but would certainly be an aid in understanding the way Stack.PHP works.
Getting Stack.PHP installed is quite simple. Begin by downloading the source archive from its Launchpad page, being sure to download one of the 0.4.x series releases. Once you have downloaded the archive, extract its contents. Copy the 'src' folder to the source folder of your PHP project. You will probably want to (and this document assumes that you will) rename the folder to something like 'stackphp' to distinguish it from other files / folders in your project.
For the purpose of this document, we will assume you are creating a new project, so you may simply copy this folder to another empty folder which we will use to store our examples' files in.
Stack.PHP includes a test suite that runs through all of the API methods and does some internal checks to make sure the methods are performing the intended actions. You can run these tests yourself by opening the 'tests' folder in a terminal and typing:
Depending on the latency of your network connection, the test suite should finish within a minute or two.
The archives available on the download page should contain a folder 'doc' with the HTML and LaTeX documentation for the entire library. Stack.PHP uses Doxygen comments embedded directly in the source code to generate the documentation for the library. This is both easy for the developers and helpful to the end user because the documentation is very tightly integrated with the source code.
The documentation explains in detail what each class, method, and constant represents and specific information on its usage. If you have a question about a certain method or class, chances are you will find the answer in the documentation.
In order to get your feet wet working with Stack.PHP, we will begin by creating a sample application that demonstrates some key concepts. Create a file in the project folder named 'index.php' and insert the following contents:
$stackoverflow = API::Site('stackoverflow');
$response = $stackoverflow->Users()->SortByReputation()->Exec();
while($user = $response->Fetch(FALSE))
Once you have saved the file, point your web browser to the appropriate URL for the file. (Note that you cannot simply open the PHP file in the web browser, you must be accessing the PHP file through a web server which knows how to feed the file through the PHP interpreter.)
If everything works well, you will see a list of usernames from Stack Overflow. Now we will examine the code and learn what each line does.
The 'require_once' at the top of the file pulls in all of the files that contain the source for Stack.PHP. Any application that uses Stack.PHP needs that line. You will never need to include any other files to use the API request classes. (Later we will learn how to set caching options, which will require additional files to be included.)
The next line creates a Site object which represents an API interface to a Stack Exchange site. Using our Site object, we can find questions on the site, view users, etc. When we create the site, we specify the site's domain name as a parameter. We are allowed to omit the '.com' suffix as we have done in the example.
The next line consists of a set of chained methods. When we speak of chaining methods, we are referring to a set of methods that perform an action and then return the same instance of the class. This allows many methods to be invoked on a single line and makes it easier to see what's going on. In our example, we are doing the following three things:
After the call to Exec(), we end up with a paged response object. This object is used throughout the API to facilitate the retrieval of items across multiple plages.
It is important to note that no requests have been made at this point. Stack.PHP is designed to avoid making any API requests until data is actually requested from the response object. We see that happen further down in the example.
Inside of the <ul>, we see a while loop that retrieves a user one at a time. In this example, we pass the parameter FALSE to the Fetch() method - this returns only the results of the first page and returns FALSE when we have fetched all of the items on that page. We do not retrieve all of the items because that would result in hundreds of requests and take an enormous amount of time.
Because nearly every API request you make will be done with the intention of examining the results, an understanding of the way Stack.PHP returns data is critical. In the previous example, we called the Exec() method and received a PagedResponse object. This class (and to a large extent the class it derives from, Response) provides an easy iterator-like interface for stepping through the objects returned by the API.
Both classes provide a Fetch() method, which retrieves the next item in the returned data. The PagedResponse class adds an optional parameter to the Fetch() method - a value indicating whether the next page should be retrieved if all of the items on the current page have already been returned.
Both classes also provide a Total() method that returns the total number of items in the set. For paged responses, this number represents all of the items in the set and not just the number of items in the current page. If you want to find out the size of each page that is being retrieved, you can call the Pagesize() method.
The first example assumed that you knew about a Stack Exchange site and its domain name. If you do not have this information, you can enumerate all Stack Exchange sites using the API::Sites() method. The example below will list all Stack Exchange sites. (The examples that follow omit the HTML and include directives where appropriate to conserve space.)
$response = API::Sites(TRUE);
while($site = $response->Fetch())
echo '- ' . $site->Name() . "\n";
Note that the Sites() method is a bit of an exception in that it returns a paged response containing Site objects instead of simply a map of key/value pairs. The Site object provides a method Name() that returns the name of the Stack Exchange site. Also notice that we pass the parameter TRUE to the Sites() method. This instructs the method to retrieve minimal information about the site - in our example we only use the site's name.
Once we have a site object (either by specifying one manually as in our first example or by enumeration as in our second example) we can query the information on the site by using its methods. For example, we can retrieve all questions on the site using the Questions() method.
The following snippet will find questions on Super User that are unanswered and have a score of 6 or greater.
$superuser = API::Site('superuser');
Notice that instead of specifying a bunch of parameters in a single method, we specify parameters one by one in a set of methods chained together. It is easy to identify the role that this line of code plays simply by inspection instead of counting off parameter position. This is one of the main reasons Stack.PHP was designed to be used in this way.
Perhaps you want to retrieve one or two specific questions instead of all of them. The Questions() method of the Site object makes this easy. Simply pass a question ID or an array of question IDs to limit the results to those specific questions. The example below fetches the question with the ID of 1 on Stack Apps.
$question = API::Site('stackapps')->Questions(1)->Exec()->Fetch();
The above example contains a number of methods that each return a different object. The Questions() method returns a QuestionRequest object that limits the results to a single question with the ID of 1. The Exec() method returns a PagedResponse object that can be used for enumerating the questions. (In this case, there is only one.) Because there is only one question, it will be the first one retrieved by the Fetch() method.
It should be noted in the above example that if the question with the ID of 1 does not exist or could not be accessed, the method will return FALSE instead of the question data.
What happens when a request could not be completed? This could occur for a number of reasons: a network connection could not be established, the API may be down for maintenance, or perhaps the API contains a bug that returns unexpected data. What will happen under those circumstances?
Stack.PHP code is carefully designed to catch these "exceptional" circumstances and handle them in a proper manner. In most cases, this means that Stack.PHP will throw an exception. Stack.PHP uses a special class (APIException) that derives from PHP's Exception class and provides some additional details about the exception that occurred.
Let's examine the following example:
$serverfault = API::Site('serverfault');
The second line has a very obvious bug: it is requesting a question with an ID of 'a' which is not a valid number. The API will most likely return an error, triggering an exception from within the Stack.PHP code.
Unless you have wrapped the code in a try block or it is being interpreted using eval(), this exception will likely terminate your application with a fatal error.
If you want to avoid this situation and display some sort of error or perform some type of fallback action, you will need to wrap the code in a try block and add a catch block to perform some action when an exception is raised. This allows your program to report the error to the client. Below is an example of what this might look like.
$response = $serverfault->Questions('a')->Exec();
$question = $response->Fetch();
Because the example fetches a question with an ID of 'a' which logically does not exist, the API method invoked behind the scenes will return an error object which Stack.PHP will detect. An exception (of type APIException) will be raised and execution will immediately jump to the contents of the catch block. In the example, the catch block simply prints the exception object which will display a brief explanation of the error.
The APIException object also contains a few other methods that can provide some more information on the error that occurred such as the URL of the last request or the error code returned by the API.
All of the examples we have looked at so far issue API requests as necessary to retrieve the data. If the application is rarely used or receives a relatively light amount of traffic, this it not a problem. However, if the application receives a significant amount of traffic it will be issuing a lot of duplicate requests and most of those requests will return duplicate responses.
Is there a way to avoid this? Thankfully the answer is yes - Stack.PHP ships with a built in cache manager that will temporarily store the responses received from the API to help cut down on bandwidth and network requests. This cache is an optional component and will only function when enabled.
Below is an example that demonstrates how to create a simple in-memory cache that will store the responses returned by the API for the duration of the script's execution.
What effect does this line of code have? The first time a request is made, Stack.PHP will hit the API servers as expected. However, if that same request is made later on during execution, the data stored in the cache will be returned instead of making an identical API request.
In the line above, we make use of an in-memory SQLite database. Depending on how your version of PHP was compiled and what extensions you have enabled, this may or may not work. If you receive an error reporting that the SQLite driver is not available, you will need to use a different database engine. Thankfully, Stack.PHP supports any database with a PDO driver. (You can find more information about PDO here.)
Even if you do not have access to an SQL database, you can still take advantage of caching in your application by using the FilestoreCache. This class uses the filesystem to store cached data in an identical manner to the SQLCache. The performance will likely be a little bit worse but will still provide a boost in performance compared to using no cache at all. You can construct and enable a FilestoreCache using the following example.
This snippet will create a folder named 'cache' and use that folder to store the API responses.
Both the SQLCache and FilestoreCache class will purge expired entries automatically when the cache is initialized. This is done to save storage space and keep the number of entries small for efficiency. You can override this behavior at any time by passing FALSE as a second parameter to API::SetCache(). If you want to manually force expired entries to be removed from the cache, simply call Cleanup() on the cache object. You might do this if you wanted to clear the cache using a cron job for example.
If none of the built-in cache classes suit your needs, you can easily create your own cache class by implementing the CacheBase class. You will need to consult the documentation for the CacheBase class to see what methods your class will need to implement and what function they should perform.
Using the OutputHelper class is simple:
You can then access all of the OutputHelper's static methods. The Stack.PHP archives available for download contain an 'examples' folder with a number of examples that demonstrate the usage of the OutputHelper class.