pear:rfc:pear2_standards

Summary

Information

Document Information

  • Title: PEAR2 Coding Standards
  • Version: 0.6.0
  • Status: Draft
  • Type: Standards
  • Last updated: September 20th, 2009

Author(s) Information

  • Name: Chuck Burgess
  • Email: ashnazg@php.net

Past Author(s) Information

  • Name: Gregory Beaver, Arnaud Limbourg
  • Email: cellog@php.net, arnaud@php.net

Discussion List

Introduction

PEAR 1.x is very successful at managing the universe of PEAR-installable code. The new Pyrus installer is designed to expand that universe to include code that can also be easily embedded in non-PEAR applications and that runs identically when simply unzipped and when installed. The PEAR2 repository must adhere to different coding conventions than the PEAR repository to make this possible.

This document itemizes all the changes to existing rules and coding standards.

Controversial items with regard to removal of handling dependencies have been extracted and moved to Controversial Changes

Expiration

These coding standards will expire on August 15, 2010, and must be reviewed and renewed by the next PEAR Group in order to continue.

Summary

This document describes the coding standards and coding conventions for the new PEAR2 repository.

Approach & Requirements

Packages that wish to be accepted into the PEAR2 repository must conform to these standards

Definition

Minimum PHP Dependency

The minimum PHP version supported will be the earliest version that supports namespaces, PHP version 5.3.0.

Requirement

No Exceptions to this rule

Package.xml 2.0

package.xml 2.0 or newer is required for all packages

Requirement

No Exceptions to this rule

Namespace prefix

All classes and functions must have a namespace of at the minimum PEAR2. An example:

 <?php
 namespace PEAR2;
 class MyClass {}
 ?>

Classes may use longer namespaces, for instance an HTTP_Request class may instead choose to use this declarative syntax:

 <?php
 namespace PEAR2\HTTP;
 class Request {}
 ?>

As such, underscores are no longer required of any classes if there is a namespace. Class PEAR2_HTTP_Request instead becomes PEAR2\HTTP\Request.

Package names, however, will use underscores, making PEAR2_HTTP_Request the package name.

Requirement

No Exceptions to this rule

use of include/require/require_once/include_once not allowed

Rule

include/require/require_once/include_once is not allowed for loading class files. Users will be expected to load files either with autoload() or a customized solution for more advanced users.

Instead, classes should simply be used. import with a comment describing the class's location must be used to document all internal dependencies (as written below). Instead of:

 <?php
 require_once 'PEAR2/OtherPackage.php';
 $class = new \PEAR2\OtherPackage;
 ?>

this class should simply be used:

 <?php
 $class = new \PEAR2\OtherPackage;
 ?>
 

This allows packages to work without modification no matter how they are structured on-disk, including running out of a single large file, inside a phar archive, and provides much-needed flexibility.

Rationale

require_once introduces a rigidity to package structure that limits the possible uses of a PEAR package. Some of the problems:

  • require_once can introduce up to a 10% performance penalty on high-volume sites using multi-processor web servers due to increased latency. However, most users would experience at most 2% performance penalty on single-processor systems (as measured by Yahoo! engineer Gopal Vijayaraghavan)
  • include_path is required in order to use a package. This makes it difficult to bundle a PEAR package within another application with its own include_path, to create a single file containing needed classes, to move a PEAR package to a phar archive without extensive source code modification.
  • when top-level require_once is mixed with conditional require_once, this can result in code that is uncacheable by opcode caches such as APC, which will be bundled with PHP 6.
  • relative require_once requires that include_path already be set up to the correct value, making it impossible to use a package without proper include_path

Some of the benefits of require_once:

  • you know right away if a file is missing, with a Fatal Error: missing file X (this is mitigated by using autoload() with PEAR2_Autoload())
  • end-users don't need to know what files are within a package to use it. (also mitigated by using autoload() with PEAR2_Autoload())

The removal of require_once necessitates another method for loading internal dependencies, both files within a package and external files. This proposal introduces 2 possible methods for doing this:

  • use autoload() in conjunction with PEAR2's custom autoload solution (found here in svn)
  • construct a customized solution for loading needed files

In all cases, the onus of loading needed files is shifted to the end user. However, for beginning users, the only required step is to load PEAR2/Autoload.php, which will be always bundled with new packages, but only extracted if used as unzip-and-go (pyrus would simply install the dependency on PEAR2, which would contain the needed base files PEAR2_Exception and PEAR2_Autoload).

<?php
require '/full/path/to/PEAR2/Autoload.php';
// now you can start using all PEAR2 packages
?>

PEAR2/Autoload.php automatically sets up include_path if it does not contain the correct value, and also automatically declares autoload() if the user has not defined it.

Requirement

No Exceptions to this rule

Loading other classes

Inside optional component loading methods (like factory or driver loading, etc.) class_exists($classname, true) should be used where a “class not found” fatal error would be confusing. For example, when loading a driver, a graceful exit via exception with helpful error message is preferable to the fatal error:

 <?php
 if (!class_exists("\PEAR2\PackageName\Driver\$class", true)) {
     throw new \PEAR2\PackageName\Exception('Unknown driver ' . $class .
               ', be sure the driver exists and is loaded prior to use');
 }
 ?>

Requirement

This rule is optional and is a suggested coding practice

Directory structure

Follows the directory structure in the PEAR2 Subversion repository:

 PEAR2/Package_Name/
                src/      <-- all role="php"
                data/     <-- all role="data"
                tests/    <-- all role="tests"
                doc/      <-- all role="doc"
                www/      <-- all role="www"
                examples/ <-- role="doc" example files (php executable
                                         files that exemplify package usage)

Note that all package.xml files must specify a baseinstalldir of “/” for the src/ directory:

 <contents>
  <dir name="/">
   <dir name="src" baseinstalldir="/">
 ...
 </contents>

Requirement

Exceptions may be made to this rule with approval from the PEAR Group

Class-to-file convention

All public classes must be in their own file with underscores (_) or namespace separators (\) replaced by directory separator, so that PEAR2_PackageName_Base class or PEAR2\PackageName\Base class is always located in PEAR2/PackageName/Base.php (this is required to make autoload work).

Requirement

Exceptions may be made to this rule only with explicit approval from the PEAR Group via a public vote.

Class Naming convention

The naming of individual classes follows these rules:

  • Start with capital letter, e.g. class Foo {}
  • CamelCase for multi-worded class names, e.g. class FooBarBaz {}
  • Abbreviations only start with capital letter, e.g. class MrClean {}
  • Acronyms should be fully capitalized, e.g. class PEARTree {}
  • syntax/scope hints in the class name must be suffixed rather than prefixed, e.g. abstract class FooAbstract {}
    • the suffix should be a full legible word, not a cryptic letter/abbreviation (e.g. FooAbst, FooA)
    • a suffix for an Abstract or Interface class name is *required*

The only exception to the Interface suffix requirement is the base package exception, which must be named simply “Exception”.

Examples

Here are some real-life examples gleaned from PEAR1 packages:

  • “class Text_Diff” in /Text/Diff.php becomes “class Diff”
  • “class DB_DataObject” in /DB_DataObject/DataObject.php becomes “class DataObject”
  • “class Auth_PrefManager” in /Auth_PrefManager/PrefManager.php becomes “class PrefManager”
  • “class Services_Amazon_SQS” in Services_Amazon_SQS/Services/Amazon/SQS.php becomes “class SQS”
  • “abstract class PHP_CodeSniffer_Standards_AbstractPatternSniff” in PHP_CodeSniffer/CodeSniffer/Standards/AbstractPatternSniff.php becomes “abstract class PatternSniffAbstract”
  • “interface Testing_DocTest_RunnerInterface” in Testing/DocTest/RunnerInterface.php becomes “interface RunnerInterface”, or perhaps “interface RunnableInterface”

Requirement

The only exception to this rule is the Base Package Exception Interface, which is specifically required to be named “vendor\Package\Exception” rather than “vendor\Package\ExceptionInterface”.

Base Exception interface

Each package must define a base Exception interface which is implemented by any exception thrown within the package.

PEAR2/PackageName/Exception.php

 <?php 
 namespace PEAR2\PackageName;
 interface Exception {}

SPL exceptions are encouraged, and should be used when possible.

Extending SPL Example: PEAR2/PackageName/UnexpectedValueException.php

 <?php
 namespace PEAR2\PackageName;
 class UnexpectedValueException extends \UnexpectedValueException implements Exception {}

Exception throwing example:

 <?php
 namespace PEAR2\PackageName;
 class Foo
 {
     function run($method)
     {
         switch($method) {
             case 'add':
             case 'del':
             case 'up':
                 $this->$method();
             break;
             default:
                 throw new UnexpectedValueException($method . ' is not a valid method.');
         }
     }
 }

User exception catching example:

 <?php
 require_once 'PEAR2/Autoload.php';
 try {
     $p = new \PEAR2\PackageName\Foo();
     $p->run('bar');
 } catch (\PEAR2\PackageName\Exception $e) {
     echo "Caught exception from PEAR2\\PackageName";
 }

Requirement

No Exceptions to this rule

Data files

package.xml replacement tasks should not be used to retrieve path locations for php, data, or www files. Replacements are still allowed in doc and test files.

The installation structure of a package has implicit php_dir/src installation location, and data files are always located in php_dir/data/channel/PackageName/. To retrieve a data file within PEAR2/PackageName/Subfile.php, you would use this code:

 <?php
 ...
 // retrieve data from info.txt
 $info = file_get_contents(dirname(__FILE__) . '/../../../data/pear2.php.net/PEAR2_PackageName/info.txt');
 ?>

Requirement

No Exceptions to this rule

Security Considerations

None

pear/rfc/pear2_standards.txt · Last modified: 2011/04/06 12:59 (external edit)