PHP
Article

Generating PHP Documentation with Sami

By Younes Rafie

Documenting your methods, classes, and functions is becoming second nature for everyone, so it makes sense to have a way to generate a separate documentation instead of navigating through the source code. In this article, I’m going to introduce you to Sami, the new API documentation generator.

Image of a large tome

What Is a DocBlock?

A DocBlock is a multi-line comment inserted at the top of an implementation (Class, Interface, Method, Attribute…etc). To clarify this, let’s use some code snippets from Laravel.

abstract class Manager
{
  /**
   * The application instance.
   *
   * @var \Illuminate\Foundation\Application
   */
  protected $app;
  

  /**
   * Create a new manager instance.
   *
   * @param \Illuminate\Foundation\Application $app
   * @return void
   */
  public function __construct($app)
  {
    $this->app = $app;
  }
}

The DocBlock must start with a /**, end with a */, and every line in between should start with a *.
When defining a class attribute or a method, we write a description, and one or more annotations to define more information about the implementation. In these examples, the @param and @var annotation tags were used. You can visit the documentation for each of these annotations to view a list of annotations phpDocumentor supports.

API Documentation Generators

There are many API documentation generators out there, but one of the best is phpDocumentor. One of the main reasons I like Sami, however, is the ability to use your versioned documentation on Github, and you can also use Twig for generating the templates.

Installing Sami

There are two ways to install Sami. The first one is to download the PHAR archive and run it using php.

php sami.phar

The other way is through Composer. You can run the composer require sami/sami:3.0.* command to add the package to your project.

php vendor/sami/sami/sami.php

Sami command

Cloning Laravel’s Documentation

For our example I will generate documentation for the Laravel Illuminate package.

git clone git@github.com:laravel/framework.git docs

Now our folder structure looks like the following.

docs/
vendor/
composer.json

The update command is responsible for refreshing the documentation and it’s used like the following.

php vendor/sami/sami/sami.php update config/config.php

Where config.php is the file that describes your documentation structure and how the output is rendered.

Configuration

The configuration file must return an instance of Sami\Sami. It accepts a Symfony\Component\Finder\Finder instance and an array of options.

// config/config.php

$dir = __DIR__ . '/../docs';

$iterator = Symfony\Component\Finder\Finder::create()
    ->files()
    ->name('*.php')
    ->exclude('build')
    ->exclude('tests')
    ->in($dir);

$options = [
    'theme'                => 'default',
    'title'                => 'Laravel API Documentation',
    'build_dir'            => __DIR__ . '/../build/laravel',
    'cache_dir'            => __DIR__ . '/../cache/laravel',
];

$sami = new Sami\Sami($iterator, $options);

return $sami;

The $dir variable holds our source file location. The $iterator will get all files and select *.php and exclude the build and test directories inside our source path.
The $options variable is self explanatory. The theme is set to default, but we will talk about making your own theme later on. The build_dir holds our output files, and the cache_dir is used for Twig cache, while title is the title of the generated documentation.

Now, open your command line and run the previous update command.

php vendor/sami/sami/sami.php update config/config.php

Sami update

After the command has executed, you can run the built-in PHP server to see how your documentation works. Run php -S localhost:8000 -t build/, and visit http://localhost:8000/laravel/ to see the result.

Using Git Versioning

I mentioned before that one of the main reasons I like Sami is because of its Git versioning integration. The options['versions'] parameter accepts a Sami\Version\GitVersionCollection which holds your Git repository configuration. As an example, let’s create the documentation for the 5.0 and 4.2 branches.

$dir = __DIR__ . '/../docs/src';

$iterator = Symfony\Component\Finder\Finder::create()
    ->files()
    ->name('*.php')
    ->in($dir);

$versions = Sami\Version\GitVersionCollection::create($dir)
    ->add('5.0', 'Master')
    ->add('4.2', '4.2');


$options = [
    'theme'                => 'default',
    'versions'             => $versions,
    'title'                => 'Laravel API Documentation',
    'build_dir'            => __DIR__ . '/../build/laravel/%version%',
    'cache_dir'            => __DIR__ . '/../cache/laravel/%version%',
];

$sami = new Sami\Sami($iterator, $options);

return $sami;

When using versions, your build_dir and cache_dir must contain the %version% tag. The second parameter for the add method is the label displayed inside a select option. You can also use the addFromTags, setFilter methods to filter versions.

php vendor/sami/sami/sami.php update config/config.php

Now your build directory will contain a folder for each version, and the user will have the ability to choose which one he wants to read.

Creating Themes

Until now, we’ve only used the default theme, but Sami is flexible enough to give us the ability to create our own themes.

The vendor/sami/sami/Sami/Resources/themes folder is where the default theme is. However, you must not place your own themes there. Sami provides a way to add your own themes path to the lookup path.

// config/config.php

$templates = $sami['template_dirs'];
$templates[] = __DIR__ . '/../themes/';

$sami['template_dirs'] = $templates;

Now we can have our themes folder in the root of our application. To create a new theme, you need to create a folder and put a manifest.yml file inside it.

// themes/laravel/manifest.yml

name: laravel
parent: default

Our new theme is called laravel and it will extend the default theme properties. As an example, we will try to add some assets to the page and override the behavior of the template.

Adding Assets

Let’s create a css folder inside our theme folder and create a laravel.css file inside it.

// themes/laravel/css/laravel.css

#api-tree a {
    color: #F4645F;
}
// themes/laravel/manifest.yml
…
static:
    'css/laravel.css': 'css/laravel.css'

The static configuration section tells Sami to copy the files as they are inside the specified destination. The build folder will contain the new file inside build/laravel/%version%/css/laravel.css.

// themes/laravel/manifest.yml
…
global:
    'layout/base.twig': 'layout/base.twig'
// themes/laravel/layout/base.twig

{% extends 'default/layout/base.twig' %}

{% block head %}
    {{ parent()  }}
    <link rel="stylesheet" href="css/laravel.css" />
{% endblock %}

Every time Sami wants to load the base layout, it will use the defined file inside your theme. We extend the default base layout to keep the default look and feel, and we inject our stylesheet link to the head block. Calling The parent() function will cause Twig to keep any existing content in the head block, and output it before our link tag.

// config/config.php

$options = [
    'theme'  => 'laravel',
    //...
];
php vendor/sami/sami/sami.php render config/config.php --force

By default Sami will not reload the documentation if nothing has changed. However, using the --force flag will force it to refresh the files. Visit the documentation page in your browser to see that the color of the left navigation links has changed.

Changing Markup

// themes/laravel/manifest.yml

global:
    'namespaces.twig': 'namespaces.twig'

As the name suggests, the namespaces file defines how our namespace content is rendered.

// themes/laravel/namespaces.twig

{% extends 'default/namespaces.twig' %}
{% from "macros.twig" %}

If you open the default/namespaces.twig page, you’ll see that Sami is using macros to simplify the process of extending templates. We will create our own macros.twig file to override the default markup.
Because Twig doesn’t support inheriting and overriding a function inside a macro, I’m going to copy and paste the default theme macros and start editing them.

// themes/laravel/macros.twig

{% macro render_classes(classes) -%}
    <div class="container-fluid underlined">
        {% for class in classes %}
            <div class="row">
                <div class="col-md-6">
                    {% if class.isInterface %}
                        <span class="label label-primary">I</span>
                    {% else %}
                        <span class="label label-info">C</span>
                    {% endif %}

                    {{ _self.class_link(class, true) }}
                </div>
                <div class="col-md-6">
                    {{ class.shortdesc|desc(class) }}
                </div>
            </div>
        {% endfor %}
    </div>
{%- endmacro %}

The only thing we’ve changed in this macro is the way we distinguish a class from an interface. Sami used to emphasize the interface, but we are going to insert a colored label before every item depending on its type instead.
We didn’t change much on the page, but you may want to try and use bootstrap styles on your pages, make the menu more responsive, and so on.
Now, after regenerating the documentation you can see that when you list a namespace, the classes and interfaces are preceded by labels.

Final

Conclusion

In this article we introduced a new Symfony tool that can help you manage the documentation of your package. You can also create a unique theme for your package docs. You can find the final result of our example on Github. If you have any questions or comments you can post them below.


Recommended
Sponsors
Because We Like You
Free Ebooks!

Grab SitePoint's top 10 web dev and design ebooks, completely free!

Get the latest in PHP, once a week, for free.