Generating PHP Documentation with Sami
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.
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
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
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.
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.