phalcon/quill

API documentation generator for Zephir and PHP sources

Maintainers

Package info

github.com/phalcon/quill

pkg:composer/phalcon/quill

Transparency log

Fund package maintenance!

phalcon

Open Collective

Statistics

Installs: 467

Dependents: 1

Suggesters: 0

Stars: 0

Open Issues: 2

v0.1.0 2026-08-02 02:24 UTC

This package is auto-updated.

Last update: 2026-08-02 02:26:33 UTC


README

Latest Version PHP Version Total Downloads License

Quill CI Quality Gate Status Coverage PDS Skeleton

Discord Contributors OpenCollective Backers OpenCollective Sponsors

API documentation generator for Zephir and PHP sources.

Quill reads a source tree into a typed model, then renders that model. Readers know one language and nothing about output; formatters know one output format and nothing about the language it came from.

ZephirReader  (phalcon/zephir)   ─┐                        ┌─→ MarkdownFormatter  (mkdocs pages)
                                  ├─→  Model  ─→ toArray() ─┤
PhpReader     (nikic/php-parser) ─┘   (object graph)        └─→ JsonFormatter      (model document)

Requirements

  • PHP ^8.1
  • phalcon/zephir to read .zep sources; selecting language: zephir without it fails with an explanation rather than a "class not found"

nikic/php-parser is a hard dependency and comes with quill - the PHP reader is not optional the way the Zephir one is.

Install

composer require --dev phalcon/quill

Usage

vendor/bin/quill generate                             every page, using ./quill.php
vendor/bin/quill generate encryption                  only pages matching the filter
vendor/bin/quill generate --format=json               one model document instead
vendor/bin/quill parity left.json right.json          structural differences
vendor/bin/quill docblocks left.json right.json out.csv

Options

Option Purpose
--config=<path> explicit path to quill.php, default ./quill.php
--output=<dir> destination override for one run; assets follow the documents
--format=<name> markdown (default) or json
--help, -h usage
<filter> positional; narrows what is written, matched case-insensitively

The registry is always built from every source file regardless of the filter, so cross-page links stay correct.

Configuration

Everything project-specific lives in a quill.php at the project root. Nothing about any particular repository is compiled into quill.

<?php

return [
    'language'   => 'zephir',
    'source'     => 'phalcon',
    'output'     => 'nikos/docs/api',
    'assets'     => 'nikos/docs/assets/css',
    'repository' => 'phalcon/cphalcon',
    'branch'     => '5.0.x',
    'prefix'     => 'phalcon',
    'extension'  => 'zep',
    'namespace'  => 'Phalcon',
];
Key Purpose
language selects the reader: zephir or php
source source tree to read
output where documents are written
assets where a formatter's assets are written; optional, defaults to output
repository, branch, prefix build the "Source on GitHub" link: https://github.com/<repository>/blob/<branch>/<prefix>/<path>
extension file extension the reader collects
namespace root namespace; headings drop it and page names carry it lowercased

source, output and assets are relative to quill.php unless they start with a slash. Every key except assets is required and must be a non-empty string; anything missing raises MissingConfigurationKey naming the key.

Splitting output from assets lets the destination mirror the layout of whatever consumes it. With the values above, cp -r nikos/docs/* <site>/docs/ lands the pages and the stylesheet where each belongs.

What generate writes

  • one page per top-level namespace segment, named <namespace>_<segment>.<ext>
  • an index page linking to the rest
  • the formatter's static assets, if it has any

A complete run also prunes: documents in the output directory that this run did not produce are deleted, so a source namespace that disappears takes its page with it. A filtered run is deliberately partial and never prunes. Pruning is scoped to the formatter's own extension - anything else in the directory belongs to someone else.

Formatters

markdown json
Output one page per namespace, plus an index one model.json
Assets api.css, the stylesheet its markup depends on none
Private members filtered out present, with visibility
Enums rendered as classes structure.keyword: enum
Traits Trait badge, plus a Used by list structure.keyword: trait

The model is deliberately complete and the Markdown formatter is deliberately opinionated: anything a reader can observe cheaply goes into the model even when today's formatters ignore it, so adding a formatter never means revisiting a reader.

api.css carries selectors only. Colors come from --api-* custom properties it reads but does not define, which leaves the palette - and light and dark - to the site rendering the pages.

The model as an integration point

ClassDefinition::toArray() serializes a whole definition and carries a version. It is a published format the moment anything reads it, so treat a shape change as a version bump. A document written by one installation is read back by another, and parity refuses a document whose version it does not recognize rather than reporting the moved keys as differences.

A ClassDefinition is six things: a Location (fqcn, namespace, relPath), a Structure (keyword plus modifiers, null rather than false where they do not apply), a description, Imports, Relations and Members. The serialization mirrors that graph exactly.

uses and traits are different relations that share a keyword: uses are the file's namespace imports, traits are what the class body pulls in. Registry inverts the latter into usedBy().

Names in Relations are absolute and backslash-prefixed. Both languages spell a parent three ways - \Foo, Foo behind a use, or Foo meaning the sibling in the same namespace - and the readers resolve all three as they read, so two trees that agree cannot look like they disagree.

Comparing two implementations

parity and docblocks both take two model documents, normally one per implementation:

vendor/bin/quill generate --format=json --config=cphalcon/quill.php
vendor/bin/quill generate --format=json --config=phalcon/quill.php

parity reports definitions present on one side only and, for shared ones, which members differ. It exits non-zero when anything differs, so it can gate a build.

docblocks writes the documentation disagreements to a spreadsheet, one row per difference with a winner column to fill in. Rows where one side is blank are pre-filled; the rest are a human decision. Nothing here edits source.

Development

docker compose up -d --build
docker exec -w /srv quill-8.1 composer install
docker exec -w /srv quill-8.1 composer test
docker exec -w /srv quill-8.1 composer analyze
docker exec -w /srv quill-8.1 composer cs

quill-8.1 is the floor and where the byte-for-byte comparison runs; quill-8.5 covers deprecations. The suite must pass on both.

License

BSD-3-Clause. See LICENSE.