Contributing to the PHP Manual

How to contribute to PHP documentation

Jun 18, 2015

If you've been wanting to contribute to PHP internals, starting with the documentation can be a great entry point; especially because it doesn't require dusting off those old C books from college.

But knowing where to start can be tricky since information on how to contribute to the docs is scattered across the internet.

This article is a step-by-step guide of how to contribute documentation to the PHP manual. My hope is that you can use this guide to:

  1. Write documentation for extensions that don't have any documentation yet
  2. Fix type-o's or add more documentation to existing docs

The quick & dirty way

There is a quick & dirty way to edit the docs. You can log into edit.php.net with your GitHub or Facebook account and see the documentation repo in a GUI.

If you click on "All Files" and toggle the "Repository" folder, you'll be able to browse and edit all the doc files. This can be handy for quick & dirty edits. All your changes will be reviewed by someone with proper permission ("docs karma") to commit them.

But if you're going to be making some bigger contributions to the PHP documentation manual, you'll want to set up a proper documentation contribution environment on your machine.

The environment

Before we begin, I'm assuming:

We'll make a directory were we can put all the tools & repos we'll need to start contributing to the docs.

$ mkdir ~/php-docs
$ cd ~/php-docs

Now that we're cd'ed into the php-docs directory, we need to download some stuff.

The documentation repo

The PHP source (php-src) is versioned controlled by git which is nice. Unfortunately the documentation is version controlled with svn. Doh!

We'll need to clone the documentation repo down to our machine. So from the php-docs directory:

$ svn co https://svn.php.net/repository/phpdoc/modules/doc-en doc-en
$ cd ./doc-en

You get two repos! When you check out the repository/phpdoc/modules/* repo, it will download two separate repos for you. One repo will be in doc-base and the other will be in en. When you make commits to either repo, you'll need to do a svn commit from the correct directory.

If you want to pull down a different language, just use the two-letter country code in place of en. For example, to download the French docs:

$ svn co https://svn.php.net/repository/phpdoc/modules/doc-fr doc-fr
$ cd ./doc-fr

The DocBook format

If you poke around the repo, you'll noticed a bunch of weird .xml files. These are the raw docs files before they are built into html or php. The XML format is DocBook. You have to keep in mind that the PHP docs were implemented way before markdown was the new hotness.

The reference docs

The main docs for all the functions/methods/etc. are found in the /en/reference/ directory. Each extension has its own directory. You'll noticed for each extension, they all follow a certain file structure.

Check out some of the files to familiarize yourself with the file structure and DocBook syntax.

PhD: The PHP docs generator

So how does one convert all these DocBook XML files into something useful? With a tool called PhD. Let's download it.

$ cd ~/php-docs
$ git clone http://git.php.net/repository/phd.git
$ cd ./phd

Make sure you can see the help output.

$ php render.php --help

Validate the DocBook XML

Because XML is overly verbose, easy to screw up, & just properly awful, we need to make sure that the XML is valid in the eyes of the computer. The doc-base repo has a script for validating the XML & generating some cache files.

It's a good idea to run this before you start mucking around with the XML files since you'll probably break something like I did.

$ cd ~/php-docs/doc-en
$ php doc-base/configure.php

If you see a bunch of output that ends with an ASCII cat picture, you're in business.

Build the docs

Now that we have validated the XML files and generated some config cache we can use PhD to generate the php files that will be used in the php.net site. For this example we're going to generate the files in the ~/php-docs/rendered-docs directory. This directory will be created for us when we run the generator.

$ cd ~/php-docs/phd
$ php render.php --docbook ~/php-docs/doc-en/doc-base/.manual.xml --package PHP --format php --output ~/php-docs/rendered-docs

This should take some time to generate all the docs (there's a lot of crap in PHP), but once it's finished, you should see a bunch of output with the last line being:

[16:30:49 - Rendering Format      ] Finished rendering

Yay! The docs have been converted from DocBook XML files to .php files. But you might be asking yourself, "How do I see a preview of all this stuff?" The answer is to download the php.net site and run it from your machine.

Download the php.net site

The final step in getting our environment set up to contribute to the PHP documentation site, is to download & run php.net on your local machine.

The php.net site is version controlled with git so you can clone the repo to your machine.

$ cd ~/php-docs/
$ git clone http://git.php.net/repository/web/php.git php.net

The repo comes with a bare-bones version of the reference docs. So we need to delete the bare-bones version and create a symbolic link to our generated docs.

$ cd ~/php-docs/php.net/manual
$ rm -Rf en/
$ ln -s ~/php-docs/rendered-docs/php-web en

The final step is to run a web server from the root php.net repo and browse the site.

$ cd ~/php-docs/php.net/
$ php -S 0.0.0.0:4000

Now in your browser go to http://localhost:4000/ and you should see a fully-functional php.net site running on your machine. Yay!

Make some changes

Now that you have all the tools installed, you can iterate through some changes relatively quickly. Make some changes to one of the existing extensions in ~/php-docs/doc-en/en/reference/ and regenerate the docs.

$ php ~/php-docs/doc-en/doc-base/configure.php
$ php ~/php-docs/phd/render.php --docbook ~/php-docs/doc-en/doc-base/.manual.xml --package PHP --format php --output ~/php-docs/rendered-docs

Then refresh the corresponding page in your local mirror of php.net (http://localhost:4000/) and you should see the changes.

Make sure you've familiarized yourself with some guidelines for adding docs in the PHP docs style guide.

Writing extension documentation from scratch

There are two ways to write new documentation from scratch: 1) using a skeleton file, 2) using the docgen script.

Using skeleton files

The skeleton files are located in the docs repo under ~/php-docs/doc-en/doc-base/RFC/skeletons. You'll notice a number of default DocBook XML files there for classes, functions, methods & lots more.

You can simply copy & paste (something very familiar to us PHP developers) those files into an existing extension folder or create your own extension folder following the file structure. This was the way I added the CSPRNG documentation since I co-authored random_int() and random_bytes() in PHP7.

Using the docgen script

When an extension has no documentation associated with it, you can use the docgen script found in the docs repo under ~/php-docs/doc-en/doc-base/scripts/docgen/docgen.php.

If our extension was called foobar, we could generate the DocBook XML files with the following command.

$ cd ~/php-docs/doc-en/doc-base/scripts/docgen/
$ php docgen.php -e foobar -o ~/php-docs/en/reference

You can see all the commands available in docgen with:

$ php docgen.php -h

Adding the extension to the function reference

After you've added all the docs for the extension, you'll need to edit the ~/php-docs/doc-en/doc-base/manual.xml.in and place your extension under the appropriate category. This will make sure it shows up under the function reference and will give it a pretty breadcrumb nav at the top of each function/method doc page.

Submitting your changes

PHP internals works on a karma system. You can't commit your changes to the SVN docs repo unless you have the karma to do so.

If you're just making a few quick changes via the edit.php.net site, then you can get by with not having "docs karma". But if you wanted to make more significant modifications to the docs, you'll need to get karma.

Getting docs karma

How do you get karma? That's really the million-dollar tribal-knowledge-only question.

I tried to get it directly from @derickr via twitter...

...which turned out to be the wrong way.

Submit a formal request

To get docs karma, you'll need to fill out the request-git-account form and then do some follow-up. The request form is the official way to gain access to the php repos for both svn & git. If you are approved, you'll be granted an @php.net account with permission to commit to the docs repos.

Follow up the formal request with reasons

But the php karma lords aren't handing out docs karma willy-nilly. You'll need to have some good reasons for them to approve you. So you'll want to follow up your formal request with the doc karma lords via the phpdoc@lists.php.net mailing list. Let them know, "Hey can you give me docs karma for x & y reasons..." Or even better, if you've made docs contributions via the quick-and-dirty way, then mention those or any other ways you've contributed to internals.

If you haven't contributed anything yet, tell them you've set up all the docs repos locally and have made x & y contributions and send some screenshots of the work you're trying to submit like I did.

Not only did I show them all the work I had done on the docs, but I've meet a lot of internals folks in person at a number of PHP conferences, and some of them knew me from my CSPRNG RFC, and others saw all the stupid questions I would ask in the stackoverflow PHP chatroom about php-src. So really the moral of the story here is to get involved in the PHP community if you haven't already.

You can also follow up with your formal request for docs karma in the #php.doc IRC channel on Efnet. Although I don't use IRC so I know not about such things. Apparently lots of internals folks like IRC.

There are, however, other ways to get docs karma. Using the official form and then following up is the only way to docs get karma. Thanks for the correction salathe!

Not all karma's are the same. If you'd like to add a feature to PHP, you'll need to go through the RFC process. Before you can create an RFC, you'll need RFC karma. RFC karma does not require you have a @php.net account but you will need a wiki account. Then you can request RFC karma for your account via the php internals list like I did. See, "how to create an RFC" for more info.

Submitting your changes

Once you have sufficient karma, you can push your changes up to the SVN repo for review.

The $Revision: number

By now you've probably noticed this line at the top of nearly every DocBook XML file:

<!-- $Revision: 299488 $ -->

Those are svn keywords that get updated automatically when you commit changes. They are used in the PHP docs project to keep track of which files need to have their corresponding translations updated.

SVN keywords have have to be explicitly enabled on your system. So before you commit anything, you'll want to edit your SVN config file ~/.subversion/config with your favorite editor in order to enable the keywords used by the PHP docs.

$ vi ~/.subversion/config

Look for the section [auto-props] (mine was at the bottom of the file) and add the following line:

*.xml = svn:eol-style=native;svn:keywords=Id Rev Revision Date LastChangedDate LastChangedRevision Author LastChangedBy HeadURL URL

Add your files and commit

Remember that when you checked out the docs repo, it checked out two separate repos for you - the base docs repo (doc-base) and the language repo (en). You'll need to cd into each one you made changes to, add any new files and commit the changes.

$ cd ~/php-docs/doc-en/en
$ svn status

Make sure you don't have any files with a ? next to them. Add any that aren't being tracked.

$ svn add path/to/yourfile.xml

Now you can commit your changes. And make your commit message a good one.

$ svn commit -m "Add foo docs for PHP 7"

The first time you commit, it should ask you for your username and password. Just use the one that you now have set up at php.net.

Now you can cd into the other repo and repeat the same steps.

$ cd ~/php-docs/doc-en/doc-base
$ svn status
$ svn add foofile.xml
$ svn commit -m "Add foo docs for PHP 7"

See your changes online

According to the PHP docs site:

Documentation is built every Friday, then synced out to the website mirrors. However, there is a special mirror at docs.php.net - where the manual is updated from sources every six hours.

And it says, "documentation is built every Friday", it really means, "documentation is built & synced to the mirrors every day". Thanks salathe for that updated info.

But at the maximum, you'll need to wait 6 hours before your wonderful contributions are available on some official PHP servers. And it will eventually make its way to all the mirror servers.

Adding docs for PHP7 and beyond

The first stable PHP 7 release is slated to come out by November 2015. And there's lots you can do to make its debut a solid one. Check out the GoPHP7-ext project to find ways to get involved with PHP 7.

One big way you can get involved is of course, writing documentation! Check out the gophp7 docs TODO-list for some ways you can start contributing to PHP 7. Thanks for the link salathe.

There are a lot of new features of PHP 7, many of which are completely undocumented. If you know of a feature that's undocumented, then you are the one to make that feature documented. If you're not sure what needs to be documented, there's a script for that!

Squashing bugs in the docs

Another way you can start contributing to the docs is by checking out the bug reports related to the docs and get to fixin' them. Thanks for the link PeeHaa.

Finding extensions that don't have docs

Belive it or not, there are some functions, classes, methods & ini settings that are part of PHP source that have no documentation at all! Luckily there's a cool script that comes with the doc-base repo that will scan the docs and flag anything that's missing.

The script can be found in ~/php-docs/doc-en/doc-base/scripts/check-missing-docs.php. It takes an argument -d which is the sqlite3 database that is generated when you run the PhD script. So in our case we'd run it like so:

$ cd ~/php-docs/doc-en/doc-base/scripts
$ php check-missing-docs.php -d ~/php-docs/rendered-docs/index.sqlite

As of June 18th, 2015, this is the output of the script running PHP 7 Alpha 1:

Scanning ini settings
Statistics and information:
Missing documentation
Array
(
    [functions] => Array
        (
            [0] => get_resources
            [1] => preg_replace_callback_array
            [2] => deflate_init
            [3] => deflate_add
            [4] => inflate_init
            [5] => inflate_add
            [6] => gmp_random_seed
            [7] => pcntl_wifcontinued
            [8] => pdo_drivers
            [9] => error_clear_last
        )

    [methods] => Array
        (
            [0] => BaseException::__construct
            [1] => BaseException::getMessage
            [2] => BaseException::getCode
            [3] => BaseException::getFile
            [4] => BaseException::getLine
            [5] => BaseException::getTrace
            [6] => BaseException::getPrevious
            [7] => BaseException::getTraceAsString
            [8] => BaseException::__toString
            [9] => Generator::getReturn
            [10] => DateTimeZone::__wakeup
            [11] => DateTimeZone::__set_state
            [12] => DateInterval::__wakeup
            [13] => DateInterval::__set_state
            [14] => DatePeriod::__wakeup
            [15] => DatePeriod::__set_state
            [16] => DatePeriod::getStartDate
            [17] => DatePeriod::getEndDate
            [18] => DatePeriod::getDateInterval
            [19] => SQLite3::openBlob
            [20] => SQLite3::enableExceptions
            [21] => SQLite3Stmt::readOnly
            [22] => DOMStringList::item
            [23] => DOMNameList::getName
            [24] => DOMNameList::getNamespaceURI
            [25] => DOMImplementationList::item
            [26] => DOMImplementationSource::getDomimplementation
            [27] => DOMImplementationSource::getDomimplementations
            [28] => DOMImplementation::getFeature
            [29] => DOMNode::compareDocumentPosition
            [30] => DOMNode::isEqualNode
            [31] => DOMNode::getFeature
            [32] => DOMNode::setUserData
            [33] => DOMNode::getUserData
            [34] => DOMDocumentFragment::__construct
            [35] => DOMDocument::adoptNode
            [36] => DOMDocument::renameNode
            [37] => DOMNamedNodeMap::setNamedItem
            [38] => DOMNamedNodeMap::removeNamedItem
            [39] => DOMNamedNodeMap::setNamedItemNS
            [40] => DOMNamedNodeMap::removeNamedItemNS
            [41] => DOMText::isElementContentWhitespace
            [42] => DOMText::replaceWholeText
            [43] => DOMUserDataHandler::handle
            [44] => DOMErrorHandler::handleError
            [45] => DOMConfiguration::setParameter
            [46] => DOMConfiguration::getParameter
            [47] => DOMConfiguration::canSetParameter
            [48] => DOMStringExtend::findOffset16
            [49] => DOMStringExtend::findOffset32
            [50] => finfo::finfo
            [51] => RecursiveRegexIterator::accept
            [52] => RecursiveTreeIterator::setPostfix
            [53] => SplFileInfo::_bad_state_ex
            [54] => SplHeap::isCorrupted
            [55] => SplPriorityQueue::getExtractFlags
            [56] => SplPriorityQueue::isCorrupted
            [57] => mysqli::connect
            [58] => mysqli::get_server_info
            [59] => mysqli::mysqli
            [60] => mysqli::escape_string
            [61] => mysqli_result::__construct
            [62] => mysqli_result::close
            [63] => mysqli_result::free_result
            [64] => mysqli_stmt::num_rows
            [65] => PDO::__wakeup
            [66] => PDO::__sleep
            [67] => PDOStatement::__wakeup
            [68] => PDOStatement::__sleep
            [69] => ReflectionFunctionAbstract::hasReturnType
            [70] => ReflectionFunctionAbstract::getReturnType
            [71] => ReflectionGenerator::__construct
            [72] => ReflectionGenerator::getExecutingLine
            [73] => ReflectionGenerator::getExecutingFile
            [74] => ReflectionGenerator::getTrace
            [75] => ReflectionGenerator::getFunction
            [76] => ReflectionGenerator::getThis
            [77] => ReflectionGenerator::getExecutingGenerator
            [78] => ReflectionParameter::hasType
            [79] => ReflectionParameter::getType
            [80] => ReflectionType::allowsNull
            [81] => ReflectionType::isBuiltin
            [82] => ReflectionType::__toString
            [83] => ReflectionClass::isAnonymous
            [84] => Phar::__destruct
            [85] => Phar::getAlias
            [86] => Phar::getPath
            [87] => PharData::__destruct
            [88] => PharData::count
            [89] => PharData::getAlias
            [90] => PharData::getPath
            [91] => PharData::getMetadata
            [92] => PharData::getModified
            [93] => PharData::getSignature
            [94] => PharData::getStub
            [95] => PharData::getVersion
            [96] => PharData::hasMetadata
            [97] => PharData::isBuffering
            [98] => PharData::isCompressed
            [99] => PharData::isFileFormat
            [100] => PharData::offsetExists
            [101] => PharData::offsetGet
            [102] => PharData::setMetadata
            [103] => PharData::setSignatureAlgorithm
            [104] => PharData::startBuffering
            [105] => PharData::stopBuffering
            [106] => PharData::apiVersion
            [107] => PharData::canCompress
            [108] => PharData::canWrite
            [109] => PharData::createDefaultStub
            [110] => PharData::getSupportedCompression
            [111] => PharData::getSupportedSignatures
            [112] => PharData::interceptFileFuncs
            [113] => PharData::isValidPharFilename
            [114] => PharData::loadPhar
            [115] => PharData::mapPhar
            [116] => PharData::running
            [117] => PharData::mount
            [118] => PharData::mungServer
            [119] => PharData::unlinkArchive
            [120] => PharData::webPhar
            [121] => PharFileInfo::__destruct
            [122] => PharFileInfo::getContent
            [123] => ZipArchive::setCompressionName
            [124] => ZipArchive::setCompressionIndex
        )

    [inis] => Array
        (
            [0] => assert.exception
            [1] => gd.jpeg_ignore_warning
            [2] => highlight.comment
            [3] => highlight.default
            [4] => highlight.html
            [5] => highlight.keyword
            [6] => highlight.string
            [7] => mail.force_extra_parameters
            [8] => mbstring.http_output_conv_mimetypes
            [9] => mysqli.rollback_on_cached_plink
            [10] => pcre.jit
            [11] => report_zend_debug
            [12] => session.lazy_write
            [13] => sys_temp_dir
            [14] => unserialize_callback_func
            [15] => user_ini.cache_ttl
            [16] => user_ini.filename
            [17] => zend.assertions
        )

    [classes] => Array
        (
            [0] => stdClass
            [1] => BaseException
            [2] => EngineException
            [3] => ParseException
            [4] => TypeException
            [5] => ClosedGeneratorException
            [6] => DOMStringList
            [7] => DOMNameList
            [8] => DOMImplementationList
            [9] => DOMImplementationSource
            [10] => DOMNameSpaceNode
            [11] => DOMTypeinfo
            [12] => DOMUserDataHandler
            [13] => DOMDomError
            [14] => DOMErrorHandler
            [15] => DOMLocator
            [16] => DOMConfiguration
            [17] => DOMStringExtend
            [18] => PDORow
            [19] => ReflectionGenerator
            [20] => ReflectionType
            [21] => AssertionException
            [22] => XMLWriter
        )

)
Counts: Missing documentation
Array
(
    [methods] => 125
    [classes] => 23
    [inis] => 18
    [functions] => 10
)
Counts: Documented documentation
Array
(
    [methods] => 1034
    [classes] => 116
    [inis] => 165
    [functions] => 1437
)

According to the comments at the top of the check-missing-docs.php script, there will be some false-positives on missing docs in the following scenarios:

So what are you waiting for? Check out the source for some undocumented stuff and get to documenting!

Resources

Here are some of the online resources that helped me thought this whole process.

Here are a few other goodies.

If you found this guide helpful, say, "Hi" on twitter! I'd love to hear from you. :)