TDD: Checking the return value of a Stub

State verification is used to ensure that after a method is run, the returned value of the SUT is as expected. Of course, you may need to use Stubs on a test double or a real object to tell the object to return a value in response to a given message.

In Java, you declare a method’s return type in its method declaration, this means that the type of the return value must match the declared return type or otherwise you will get a compiler error. In PHP, for example, you dynamically type the return value within the body of the method. This means that PHP mocking libraries cannot check the type of the return value and provide guarantees about what is being verified.

This leads to the awkward situation where a refactoring may change the SUT behaviour and leave a stub broken but with passing tests. For example, consider the following:

Developer (A) creates 2 classes, Presenter and Collaborator:

class Presenter
{
    protected $collaborator;

    public function __construct(Collaborator $obj)
    {
        $this->collaborator = $obj;
    }

    public function doSomething()
    {
        $limit = 1;
        $stories = $this->collaborator->getStories($limit);
        // ...
        return $stories;
    }
}

class Collaborator
{
    public function getStories($limit)
    {
        return array();
    }
}

Then writes a test case:

class PresenterTest extends PHPUnit_Framework_TestCase
{
    // Behaviour verification
    public function testBehaviour()
    {
        $mock = $this->getMock('Collaborator', array('getStories'));
        $mock->expects($this->once())
            ->method('getStories')
            ->with(
                $this->logicalAnd(
                    $this->equalTo(1), $this->isType('integer')
                )
            );

        $presenter = new Presenter($mock);
        $presenter->doSomething();
    }

    // State verification
    public function testState()
    {
        $stub = $this->getMock('Collaborator', array('getStories'));
        $stub->expects($this->once())
            ->method('getStories')
            ->will($this->returnValue(array()));

        $presenter = new Presenter($stub);
        $data = $presenter->doSomething();

        $this->assertEquals(array(), $data);
    }
}

The Developer (A) uses a mock to verify the behaviour (a mockist practitioner) and a stub to verify the method worked correctly. The first test asserts that the expectation is met and the second one that the given condition is true. Finally, the Developer runs and watches all of the tests pass. Great!

The next day Developer (B) decides to makes some changes to the Collaborator class and return NULL if there are no stories:

class Collaborator
{
    public function getStories($limit)
    {
        $stories = array();
        if (count($stories) < 1) {
            return;
        }

        return $stories;
    }
}

The implementation of the method-under-test changed, it now returns a different data type, null instead of array. This means that our second test should fail, but it doesn’t. The test still asserts that the given condition is true, even though the return type is different. This is a problem. It means that our second test is unable to verify the correct state of the SUT (and its collaborator).

This is because most PHP mocking libraries are heavily influenced by Java (PHPUnit was originally a port of JUnit), and Java doesn’t have this problem. In PHP, the method’s return type is not a required elements of a method declaration, so developers can define it at run time and return whatever type they want.

The solution

You can use DocBlock annotations to make sure the data type of the returned value matches the one defined in the DocBlock. For this to work you need to set the return value using ReturnValue instead of PHPUnit_Framework_MockObject_Stub_Return. For example:

class PresenterTest extends PHPUnit_Framework_TestCase
{
    // State verification
    public function testState()
    {
        $stub = $this->getMock('Collaborator', array('getStories'));
        $stub->expects($this->once())
            ->method('getStories')
            ->will(new ReturnValue(array()));

        $presenter = new Presenter($stub);
        $data = $presenter->doSomething();

        $this->assertEquals(array(), $data);
    }
}

Now if you run the test it fails with the following error message:

PHPUnit_Framework_Exception: Invalid method declaration; return type required

The test also fails if the returned type doesn’t match the expected one defined in the DocBlock:

class Collaborator
{
    /**
     * @return int
     */
    public function getStories($limit)
    {
        // ...
    }
}

Error message:

PHPUnit_Framework_Exception: array does not match expected type "int"

Or, if you specify more than one data type in the DocBlock:

class Collaborator
{
    /**
     * @return array|null
     */
    public function getStories($limit)
    {
        // ...
    }
}

Error message:

PHPUnit_Framework_Exception: getStories cannot return more than one type, 2 given (array, null)

This solution is not perfect but should work in most cases.

Building a RESTful Web API with PHP and Apify

Apify is a small and powerful open source library that delivers new levels of developer productivity by simplifying the creation of RESTful architectures. You can see it in action here. Web services are a great way to extend your web application, however, adding a web API to an existing web application can be a tedious and time-consuming task. Apify takes certain common patterns found in most web services and abstracts them so that you can quickly write web APIs without having to write too much code.

Apify exposes similar APIs as the Zend Framework, so if you are familiar with the Zend Framework, then you already know how to use Apify. Take a look at the UsersController class.

Building a RESTful Web API

In Apify, Controllers handle incoming HTTP requests, interact with the model to get data, and direct domain data to the response object for display. The full request object is injected via the action method and is primarily used to query for request parameters, whether they come from a GET or POST request, or from the URL.

Creating a RESTful Web API with Apify is easy. Each action results in a response, which holds the headers and document to be sent to the user’s browser. You are responsible for generating the response object inside the action method.

class UsersController extends Controller
{
    public function indexAction($request)
    {
        // 200 OK
        return new Response();
    }
}

The response object describes the status code and any headers that are sent. The default response is always 200 OK, however, it is possible to overwrite the default status code and add additional headers:

class UsersController extends Controller
{
    public function indexAction($request)
    {
        $response = new Response();

        // 401 Unauthorized
        $response->setCode(Response::UNAUTHORIZED);

        // Cache-Control header
        $response->setCacheHeader(3600);

        // ETag header
        $response->setEtagHeader(md5($request->getUrlPath()));

        // X-RateLimit header
        $limit = 300;
        $remaining = 280;
        $response->setRateLimitHeader($limit, $remaining);

        // Raw header
        $response->addHeader('Edge-control: no-store');

        return $response;
    }
}

Content Negotiation

Apify supports sending responses in HTML, XML, RSS and JSON. In addition, it supports JSONP, which is JSON wrapped in a custom JavaScript function call. There are 3 ways to specify the format you want:

  • Appending a format extension to the end of the URL path (.html, .json, .rss or .xml)
  • Specifying the response format in the query string. This means a format=xml or format=json parameter for XML or JSON, respectively, which will override the Accept header if there is one.
  • Sending a standard Accept header in your request (text/html, application/xml or application/json).

The acceptContentTypes method indicates that the request only accepts certain content types:

class UsersController extends Controller
{
    public function indexAction($request)
    {
    	// only accept JSON and XML
        $request->acceptContentTypes(array('json', 'xml'));

        return new Response();
    }
}

Apify will render the error message according to the format of the request.

class UsersController extends Controller
{
    public function indexAction($request)
    {
        $request->acceptContentTypes(array('json', 'xml'));

    	$response = new Response();
        if (! $request->hasParam('api_key')) {
            throw new Exception('Missing parameter: api_key', Response::FORBIDDEN);
        }
        $response->api_key = $request->getParam('api_key');

        return $response;
    }
}

Request

GET /users.json

Response

Status: 403 Forbidden
Content-Type: application/json
{
    "code": 403,
    "error": {
        "message": "Missing parameter: api_key",
        "type": "Exception"
    }
}

Resourceful Routes

Apify supports REST style URL mappings where you can map different HTTP methods, such as GET, POST, PUT and DELETE, to different actions in a controller. This basic REST design principle establishes a one-to-one mapping between create, read, update, and delete (CRUD) operations and HTTP methods:

HTTP Method URL Path Action Used for
GET /users index display a list of all users
GET /users/:id show display a specific user
POST /users create create a new user
PUT /users/:id update update a specific user
DELETE /users/:id destroy delete a specific user

 

If you wish to enable RESTful mappings, add the following line to the index.php file:

try {
    $request = new Request();
    $request->enableUrlRewriting();
    $request->enableRestfulMapping();
    $request->dispatch();
} catch (Exception $e) {
    $request->catchException($e);
}

The RESTful UsersController for the above mapping will contain 5 actions as follows:

class UsersController extends Controller
{
    public function indexAction($request) {}
    public function showAction($request) {}
    public function createAction($request) {}
    public function updateAction($request) {}
    public function destroyAction($request) {}
}

By convention, each action should map to a particular CRUD operation in the database.

Building a Web Application

Building a web application can be as simple as adding a few methods to your controller. The only difference is that each method returns a view object.

class PostsController extends Controller
{
    /**
     * route: /posts/:id
     *
     * @param $request Request
     * @return View|null
     */
    public function showAction($request)
    {
        $id = $request->getParam('id');
        $post = $this->getModel('Post')->find($id);
        if (! isset($post->id)) {
            return $request->redirect('/page-not-found');
        }

        $view = $this->initView();
        $view->post = $post;
        $view->user = $request->getSession()->user

        return $view;
    }

    /**
     * route: /posts/create
     *
     * @param $request Request
     * @return View|null
     */
    public function createAction($request)
    {
        $view = $this->initView();
        if ('POST' !== $request->getMethod()) {
            return $view;
        }

        try {
            $post = new Post(array(
                'title' => $request->getPost('title'),
                'text'  => $request->getPost('text')
            ));
        } catch (ValidationException $e) {
            $view->error = $e->getMessage();
            return $view;
        }

        $id = $this->getModel('Post')->save($post);
        return $request->redirect('/posts/' . $id);
    }
}

The validation is performed inside the Post entity class. An exception is thrown if any given value causes the validation to fail. This allows you to easily implement error handling for the code in your controller.

Entity Class

You can add validation to your entity class to ensure that the values sent by the user are correct before saving them to the database:

class Post extends Entity
{
    protected $id;
    protected $title;
    protected $text;

    // sanitize and validate title (optional)
    public function setTitle($value)
    {
        $value = htmlspecialchars(trim($value), ENT_QUOTES);
        if (empty($value) || strlen($value) < 3) {
            throw new ValidationException('Invalid title');
        }
        $this->title = $title;
    }

    // sanitize text (optional)
    public function setText($value)
    {
        $this->text = htmlspecialchars(strip_tags($value), ENT_QUOTES);
    }
}

Routes

Apify provides a slimmed down version of the Zend Framework router:

$routes[] = new Route('/posts/:id',
    array(
        'controller' => 'posts',
        'action'     => 'show'
    ),
    array(
        'id'         => '\d+'
    )
);
$routes[] = new Route('/posts/create',
    array(
        'controller' => 'posts',
        'action'     => 'create'
    )
);

HTTP Request

GET /posts/1

Incoming requests are dispatched to the controller “Posts” and action “show”.

Feedback

  • If you encounter any problems, please use the issue tracker.
  • For updates follow @fedecarg on Twitter.
  • If you like Apify and use it in the wild, let me know.

Implementing Dynamic Finders and Parsing Method Expressions

Most ORMs support the concept of dynamic finders. A dynamic finder looks like a normal method invocation, but the method itself doesn’t exist, instead, it’s generated dynamically and processed via another method at runtime.

A good example of this is Ruby. When you invoke a method that doesn’t exist, it raises a NoMethodError exception, unless you define “method_missing”. Rails ActiveRecord::Base class implements some of its magic thanks to this method. For example, find_by_title(title) and find_by_title_and_date(title, date) are turned into:

find(:first, :conditions => ["title = ?", title])
find(:first, :conditions => ["title = ? AND date = ?", title, date])

What’s nice about Ruby is that the language allows you to define methods dynamically using the “define_method” method. That’s how Rails defines each dynamic finder in the class after it is first invoked, so that future attempts to use it do not run through the “method_missing” method.

Method Expressions

GORM, Grails ORM library, introduces the concept of dynamic method expressions. A method expression is made up of the prefix such as “findBy” followed by an expression that combines one or more properties. Grails takes advantage of Groovy features to provide dynamic methods:

findByTitle("Example")
findByTitleLike("Exa%")

Method expressions can also use a boolean operator to combine two criteria:

findAllByTitleLikeAndDateGreaterThan("Exampl%", '2010-03-23')

In this case we are using AND in the middle of the query to make sure both conditions are satisfied, but you could equally use OR:

findAllByTitleLikeOrDateGreaterThan("Exampl%", '2010-03-23')

Parsing Method Expressions

MethodExpressionParser is a PHP library for parsing method expressions. It’s designed to quickly and easily parse method expressions and construct conditions based on attribute names and arguments.

Description

[finderMethod]([attribute][expression][logicalOperator])?[attribute][expression]

Expressions

  • LessThan: Less than the given value
  • LessThanEquals: Less than or equal a give value
  • GreaterThan: Greater than a given value
  • GreaterThanEquals: Greater than or equal a given value
  • Like: Equivalent to a SQL like expression
  • NotEqual: Negates equality
  • IsNotNull: Not a null value (doesn’t require an argument)
  • IsNull: Is a null value (doesn’t require an argument)

Examples

findByTitleAndDate('Example', date('Y-m-d'));
SELECT * FROM book WHERE title = ? AND date = ?

findByTitleOrDate('Example', date('Y-m-d'))
SELECT * FROM book WHERE title = ? OR date = ?

findByPublisherOrTitleAndDate('Name', 'Example', date('Y-m-d'))
SELECT * FROM book WHERE publisher = ? OR (title = ? AND date = ?)

findByPublisherInAndTitle(array('Name1', 'Name2'), 'Example')
SELECT * FROM book WHERE publisher IN (?, ?) AND date = ?

findByTitleLikeAndDateNotNull('Examp%')
SELECT * FROM book WHERE title LIKE ? AND date NOT NULL

findByIdOrTitleAndDateNotNull(1, 'Example')
SELECT * FROM book WHERE (id = ?) OR (title = ? AND date NOT NULL)

Example 1:

findByTitleLikeAndDateNotNull('Examp%');

Outputs:

array
  0 =>
    array
      0 =>
        array
          'attribute' => string 'title'
          'expression' => string 'Like'
          'format' => string '%s LIKE ?'
          'placeholders' => int 1
          'argument' => string 'Examp%'
      1 =>
        array
          'attribute' => string 'date'
          'expression' => string 'NotNull'
          'format' => string '%s IS NOT NULL'
          'placeholders' => int 0
          'argument' => null

Example 2:

findByTitleAndPublisherNameOrTitleAndPublisherName('Title', 'a', 'Title', 'b');

Outputs:

array
  0 =>
    array
      0 =>
        array
          'attribute' => string 'title'
          'expression' => string 'Equals'
          'format' => string '%s = ?'
          'placeholders' => int 1
          'argument' => string 'Title'
      1 =>
        array
          'attribute' => string 'publisher_name'
          'expression' => string 'Equals'
          'format' => string '%s = ?'
          'placeholders' => int 1
          'argument' => string 'a'
  1 =>
    array
      0 =>
        array
          'attribute' => string 'title'
          'expression' => string 'Equals'
          'format' => string '%s = ?'
          'placeholders' => int 1
          'argument' => string 'Title'
      1 =>
        array
          'attribute' => string 'publisher_name'
          'expression' => string 'Equals'
          'format' => string '%s = ?'
          'placeholders' => int 1
          'argument' => string 'b'

See more examples: Project Wiki

Usage

class EntityRepository
{
    private $methodExpressionParser;

    // Return a single instance of MethodExpressionParser
    public function getMethodExpressionParser() {
    }

    // Finder methods
    public function findBy($conditions) {
        var_dump($conditions);
    }
    public function findAllBy($conditions) {
        var_dump($conditions);
    }

    // Invoke finder methods
    public function __call($method, $args) {
        if ('f' === $method{0}) {
            try {
                $result = $this->getMethodExpressionParser()->parse($method, $args);
                $finderMethod = key($result);
                $conditions = $result[$finderMethod];
            } catch (MethodExpressionParserException $e) {
                $message = sprintf('%s: %s()', $e->getMessage(), $method);
                throw new EntityRepositoryException($message);
            }
            return $this->$finderMethod($conditions);
        }

        $message = 'Invalid method call: ' . __METHOD__;
        throw new BadMethodCallException($message);
    }
}

Performance

PHP doesn’t allow you to define methods dynamically, this means that every time you invoke a finder method the parser has to search, extract and map all the attribute names and expressions. To avoid introducing this performance overhead you can cache the attribute names. For example:

class EntityRepository
{
    private $methodExpressionParser;
    private $classMetadata;

    // Return a single instance of MethodExpressionParser
    public function getMethodExpressionParser() {
    }

    // Return a single instance of ClassMetadata
    public function getClassMetadata() {
    }

    // Invoke finder methods
    public function __call($method, $args) {
        if ('f' === $method{0}) {
            $parser = $this->getMethodExpressionParser();
            $classMetadata = $this->getClassMetadata();
            try {
                $finderMethod = $parser->determineFinderMethod($method);
                if ($classMetadata->hasMissingMethod($method)) {
                    $attributes = $classMetadata->getMethodAttributes($method);
                    $conditions = $parser->map($args, $attributes);
                } else {
                    $expressions = substr($method, strlen($finderMethod));
                    $attributes = $this->extractAttributeNames($expressions);
                    $conditions = $parser->map($args, $attributes);
                    $classMetadata->setMethodAttributes($method, $attributes);
                }
            } catch (MethodExpressionParserException $e) {
                $message = sprintf('%s: %s()', $e->getMessage(), $method);
                throw new EntityRepositoryException($message);
            }
            return $this->$finderMethod($conditions);
        }

        $message = 'Invalid method call: ' . __METHOD__;
        throw new BadMethodCallException($message);
    }
}

The Expression objects are lazy-loaded, depending on the expressions found in the method name.

Extensibility

The MethodExpressionParser class was designed with extensibility in mind, allowing you to add new Expressions to the library.

abstract class Expression {
}
class EqualsExpression extends Expression {
}

Source Code

Browse source code:
http://fedecarg.com/repositories/show/expressionparser

Check out the current development trunk with:

$ svn checkout http://svn.fedecarg.com/repo/Zf/Orm

Testing Zend Framework Action Controllers With Mocks

In this post I’ll demonstrate a unit test technique for testing Zend Framework Action Controllers using Mock Objects. Unit testing controllers independently has a number of advantages:

  1. You can develop controllers test-first (TDD).
  2. It allows you to develop and test all of your controller code before developing any of the view scripts.
  3. It helps you quickly identify problems in the controller, rather than problems in one of the combination of Model, View and Controller.

The Action Controller I’m going to test has only one method, profileAction():

tests/application/controllers/UserController.php

class UserController extends Zend_Controller_Action
{
    public function profileAction()
    {
        $this->view->userId = $this->_getParam('user_id');
        return $this->render();
    }
}

tests/application/ControllerTestCase.php

class ControllerTestCase extends Zend_Test_PHPUnit_ControllerTestCase
{
    public $application;

    public function setUp()
    {
        $this->application = new Zend_Application(
            APPLICATION_ENV,
            APPLICATION_PATH . '/config/application.ini'
        );

        $this->bootstrap = array($this, 'bootstrap');
        parent::setUp();
    }

    public function tearDown()
    {
        Zend_Controller_Front::getInstance()->resetInstance();

        $this->resetRequest();
        $this->resetResponse();

        $this->request->setPost(array());
        $this->request->setQuery(array());
    }

    public function bootstrap()
    {
        $this->application->bootstrap();
    }
}

tests/application/controllers/UserControllerTest.php

require_once TESTS_PATH . '/application/ControllerTestCase.php';
require_once APPLICATION_PATH . '/controllers/UserController.php';

class UserControllerTest extends ControllerTestCase
{
    public function testStubRenderMethodCall()
    {
        $request = $this->getRequest()
            ->setRequestUri('/user/profile/1')
            ->setParams(array('user_id'=>1))
            ->setPathInfo(null);

        $response = $this->getResponse();

        $this->getFrontController()
            ->setRequest($request)
            ->setResponse($response)
            ->throwExceptions(true)
            ->returnResponse(false);

        $controller = $this->getMock(
            'UserController',
            array('render'),
            array($request, $response, $request->getParams())
        );
        $controller->expects($this->once())
                 ->method('render')
                 ->will($this->returnValue(true));

        $this->assertTrue($controller->profileAction());
        $this->assertTrue($controller->view->user_id == 1);
    }
}

You can go further making both the tests and the implementation more sophisticated. The main point is that you can build and test a controller in a way that doesn’t require a view script to be written to do so.

Zend Framework Known Issues

By default Zend_Test_PHPUnit_ControllerTestCase sets the redirector exit value to false, leading to unexpected behavior when unit testing your code. For that reason, make sure you always add a return statement after calling a utility method:

class UserController extends Zend_Controller_Action
{
    public function profileAction()
    {
        if (null == $this->_getParam('user_id', null) {
            return $this->_redirect('/');
        }
        return $this->render();
    }
}

If you want the Front Controller to throw exceptions, you have no other choice than to overwrite the dispatch method and pass a boolean TRUE to the throwExceptions() method:

class ControllerTestCase extends Zend_Test_PHPUnit_ControllerTestCase
{
    ...

    public function dispatch($url = null)
    {
        // redirector should not exit
        $redirector = Zend_Controller_Action_HelperBroker::getStaticHelper('redirector');
        $redirector->setExit(false);

        // json helper should not exit
        $json = Zend_Controller_Action_HelperBroker::getStaticHelper('json');
        $json->suppressExit = true;

        $request = $this->getRequest();
        if (null !== $url) {
            $request->setRequestUri($url);
        }
        $request->setPathInfo(null);

        $this->getFrontController()
             ->setRequest($request)
             ->setResponse($this->getResponse())
             ->throwExceptions(true)
             ->returnResponse(false);

        $this->getFrontController()->dispatch();
    }

    ...
}

The Dispatcher not only violates the DRY principle but also suffers from amnesia. The problem is that it doesn’t store the instance of the Action Controller, instead, it destroys it (Zend_Controller_Dispatcher_Standard Line 305). You can easily get around this issue by extending the standard dispatcher and overwriting the dispatch() method:

class Zf_Controller_Dispatcher_Standard extends Zend_Controller_Dispatcher_Standard
{
    ...

    public function dispatch($url = null)
    {
        ...
        Zend_Registry::set('Zend_Controller_Action', $controller);

        // Destroy the page controller instance and reflection objects
        $controller = null;
    }

This will allow you to access the view object after dispatching the request:

class ExampleControllerTest extends ControllerTestCase
{
    public function testDefaultActionRendersViewObject()
    {
        $this->dispatch('/');

        $controller = Zend_Registry::get('Zend_Controller_Action');

        $this->assertEquals('ExampleController', get_class($controller));
        $this->assertTrue(isset($controller->view));
    }

Links

PHPUnit: Testing Zend Framework Controllers
PHPUnit: Mock Objects

Increase speed and reduce bandwidth usage

Apache’s mod_deflate module provides the DEFLATE output filter that allows output from your server to be compressed before being sent to the client over the network.

There are two ways of enabling gzip compression:

  1. Using Apache’s mod_deflate
  2. Using output buffering

Encoding the output and setting the appropriate headers manually makes the code more portable. Keep in mind that there are hundreds of Linux distributions, each slightly different to significantly different. To allow portability the application should not make assumptions about the OS or config involved.

Using Apache

1. Enable mod_deflate

Debian/Ubuntu:

$ a2enmod deflate
$ /etc/init.d/apache2 force-reload

2. Configure mode_deflate

$ nano /etc/apache2/mods-enabled/deflate.conf

#
# mod_deflate configuration
#
<IfModule mod_deflate.c>
 AddOutputFilterByType DEFLATE text/plain
 AddOutputFilterByType DEFLATE text/html
 AddOutputFilterByType DEFLATE text/xml
 AddOutputFilterByType DEFLATE text/css
 AddOutputFilterByType DEFLATE application/xml
 AddOutputFilterByType DEFLATE application/xhtml+xml
 AddOutputFilterByType DEFLATE application/rss+xml
 AddOutputFilterByType DEFLATE application/javascript
 AddOutputFilterByType DEFLATE application/x-javascript

 DeflateCompressionLevel 9

 BrowserMatch ^Mozilla/4 gzip-only-text/html
 BrowserMatch ^Mozilla/4\.0[678] no-gzip
 BrowserMatch \bMSIE !no-gzip !gzip-only-text/html

 DeflateFilterNote Input instream
 DeflateFilterNote Output outstream
 DeflateFilterNote Ratio ratio
</IfModule>

Using output buffering

Create a gzip compressed string in your bootstrap file:

try {
    $frontController = Zend_Controller_Front::getInstance();
    if (@strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== false) {
        ob_start();
        $frontController->dispatch();
        $output = gzencode(ob_get_contents(), 9);
        ob_end_clean();
        header('Content-Encoding: gzip');
        echo $output;
    } else {
        $frontController->dispatch();
    }
} catch (Exeption $e) {
    if (Zend_Registry::isRegistered('Zend_Log')) {
        Zend_Registry::get('Zend_Log')->err($e->getMessage());
    }
    $message = $e->getMessage() . "\n\n" . $e->getTraceAsString();
    /* trigger event */
}

Reference

Use mod_deflate to compress Web content delivered by Apache

Format a time interval with the requested granularity

This class, a refactored version of Drupal’s format_interval function, makes it relatively easy to format an interval value. The format will automatically format as compactly as possible. For example: if the difference between the two dates is only a few hours and both dates occur on the same day, the year, month, and day parts of the date will be omitted.

class DateIntervalFormat
{
    /**
     * Format an interval value with the requested granularity.
     *
     * @param integer $timestamp The length of the interval in seconds.
     * @param integer $granularity How many different units to display in the string.
     * @return string A string representation of the interval.
     */
    public function getInterval($timestamp, $granularity = 2)
    {
        $seconds = time() - $timestamp;
        $units = array(
            '1 year|:count years' => 31536000,
            '1 week|:count weeks' => 604800,
            '1 day|:count days' => 86400,
            '1 hour|:count hours' => 3600,
            '1 min|:count min' => 60,
            '1 sec|:count sec' => 1);
        $output = '';
        foreach ($units as $key => $value) {
            $key = explode('|', $key);
            if ($seconds >= $value) {
                $count = floor($seconds / $value);
                $output .= ($output ? ' ' : '');
                if ($count == 1) {
                    $output .= $key[0];
                } else {
                    $output .= str_replace(':count', $count, $key[1]);
                }
                $seconds %= $value;
                $granularity--;
            }
            if ($granularity == 0) {
                break;
            }
        }

        return $output ? $output : '0 sec';
    }
}

Usage:

$dateFormat = new DateIntervalFormat();
$timestamp = strtotime('2009-06-21 20:46:11');
print sprintf('Submitted %s ago',  $dateFormat->getInterval($timestamp));

Outputs:

Submitted 3 days 4 hours ago

TypeFriendly: A Documentation And User Manual Builder

TypeFriendly is a documentation generation script written in PHP5. It was designed to be easy in use and it allows to achieve the first results immediately, a couple of minutes after you start the work. The script contains everything you need to write clear, multilingual documentation for your project, so that you do not have to code everything on your own.

The most important features of TypeFriendly:

  1. Modular documentation structure – it is generated from text files and the structure and navigation are generated from the file names.
  2. Simple syntax – the text is written in intuitive and clean Markdown syntax.
  3. Multilingual support and tools – TypeFriendly allows you to create your manuals in many language versions. It also contains a tool that shows whether the derived languages are up-to-date.
  4. Configurable output formats – currently, TypeFriendly is able to generate the documentation in XHTML (many pages) and XHTML (single page). There is also a third format – metadata – still under development. It will allow to import the docs to a database in order to make an on-line version with, for example, user comments.
  5. Various add-ons such as syntax highlighting, references, class description fields.
  6. Navigation generators.
  7. It is portable – works under Linux, FreeBSD and Windows. All you need is the PHP interpreter available.

TypeFriendly is distributed under the terms of GNU General Public License 3, which means that you can use, modify and share it for free.

Demo
http://static.invenzzia.org/docs/tf/0_1/book/en/index.html

Screenshots
http://www.invenzzia.org/en/projects/typefriendly/screenshots

Source Code
http://svn.invenzzia.org/browser/TypeFriendly/trunk/

Website
http://www.invenzzia.org/en/projects/typefriendly