cvs: peardoc /en/chapters rfcs-entities.xml /en/chapters/rfcs error-handling.xml version-naming.xml

[email protected] ("Christian Weiske")
Newsgroups php.pear.doc
Message-ID <cvscweiske1241356882@cvsserver>
cweiske		Sun May  3 13:21:22 2009 UTC

  Added files:                 
    /peardoc/en/chapters/rfcs	error-handling.xml 

  Modified files:              
    /peardoc/en/chapters	rfcs-entities.xml 
    /peardoc/en/chapters/rfcs	version-naming.xml 
  Log:
  Add error handling rfc (bug #11198)
cweiske-20090503132122.txt (text/plain, 16.5 KB)
http://cvs.php.net/viewvc.cgi/peardoc/en/chapters/rfcs-entities.xml?r1=1.1&r2=1.2&diff_format=u
Index: peardoc/en/chapters/rfcs-entities.xml
diff -u peardoc/en/chapters/rfcs-entities.xml:1.1 peardoc/en/chapters/rfcs-entities.xml:1.2
--- peardoc/en/chapters/rfcs-entities.xml:1.1	Sun May  3 12:50:12 2009
+++ peardoc/en/chapters/rfcs-entities.xml	Sun May  3 13:21:22 2009
@@ -1,2 +1,2 @@
-<!--&package.tools.versioncontrol-svn;-->
+&chapters.rfcs.error-handling;
 &chapters.rfcs.version-naming;
http://cvs.php.net/viewvc.cgi/peardoc/en/chapters/rfcs/version-naming.xml?r1=1.1&r2=1.2&diff_format=u
Index: peardoc/en/chapters/rfcs/version-naming.xml
diff -u peardoc/en/chapters/rfcs/version-naming.xml:1.1 peardoc/en/chapters/rfcs/version-naming.xml:1.2
--- peardoc/en/chapters/rfcs/version-naming.xml:1.1	Sun May  3 12:50:13 2009
+++ peardoc/en/chapters/rfcs/version-naming.xml	Sun May  3 13:21:22 2009
@@ -7,8 +7,8 @@
     <title>Version Naming</title>
   </info>
 
-  <para>The RFC can be found on <link
-  xlink:href="http://pear.php.net/pepr/pepr-proposal-show.php?id=65">PEPr</link>.</para>
+  <para>This RFC has been voted on in PEPr as<link
+  xlink:href="&url.pear.pepr.proposal;65">proposal #65</link>.</para>
 
   <para>As discussed previously on pear-group, where a full consensus could
   not be reached on the version naming standard, below is the proposed

http://cvs.php.net/viewvc.cgi/peardoc/en/chapters/rfcs/error-handling.xml?view=markup&rev=1.1
Index: peardoc/en/chapters/rfcs/error-handling.xml
+++ peardoc/en/chapters/rfcs/error-handling.xml
<?xml version="1.0" encoding="UTF-8"?>
<chapter version="5.0" xml:id="chapters.rfcs.error-handling"
         xmlns="http://docbook.org/ns/docbook"
         xmlns:xlink="http://www.w3.org/1999/xlink"
>
  <info>
    <title>Error Handling Guidelines for PHP5 packages</title>

    <abstract>
      <para>This document defines guidelines for error handling within PEAR,
      for PHP5 packages. It was written to cope with Exceptions, introduced in
      Zend Engine 2 as the error handling mechanism. The final objective is to
      integrate the document text into the PEAR Coding Guidelines.</para>
    </abstract>
  </info>

  <para>This RFC has been voted on in PEPr as <link
  xlink:href="&url.pear.pepr.proposal;132">proposal
  #132</link>.</para>

  <section xml:id="chapters.rfcs.error-handling.audience">
    <title>Audience</title>

    <para>This document is targeted at PHP developers writing packages for
    submission into the PEAR repository. As any coding guidelines, it is
    useful to developers using PHP in other environments. The requirements for
    reading this text are only familiarity with PHP as a programming language,
    as well as familiarity with the mechanism of Exceptions as an error
    handling mechanism.</para>

    <para>For those PHP developers unfamiliar with Exceptions, the wiki page
    from which this document was extracted provides a good introduction, as
    well as pointers to other references.</para>
  </section>

  <section xml:id="chapters.rfcs.error-handling.definition">
    <title>Definition of error</title>

    <para>An error is defined as an unexpected, invalid program state from
    which it is impossible to recover. For the sake of definition, recovery
    scope is defined as the method scope. Incomplete recovery is considered a
    recovery.</para>

    <para>One pretty straightforward example for an
    <emphasis>error</emphasis>:</para>

    <programlisting role="php">&lt;?php
/**
 * Connect to Specified Database
 *
 * @throws Example_Datasource_Exception When it can't connect
 *                                      to specified DSN.
 */
function connectDB($dsn)
{
    $this-&gt;db =&amp; DB::connect($dsn);
    if (DB::isError($this-&gt;db)) {
        throw new Example_Datasource_Exception(
            "Unable to connect to $dsn:" . $this-&gt;db-&gt;getMessage()
        );
    }
}
?&gt;</programlisting>

    <para>In this example the objective of the method is to connect to the
    given DSN. Since it can't do anything but ask PEAR DB to do it, whenever
    DB returns an error, the only option is to bail out and launch the
    exception.</para>

    <para>The next example will introduce the concept of
    <emphasis>recovery</emphasis>:</para>

    <programlisting role="php">&lt;?php
/**
 * Connect to one of the possible databases
 *
 * @throws Example_Datasource_Exception When it can't connect to
 *                                      any of the configured databases.
 * @throws Example_Config_Exception     When it can't find databases
 *                                      in the configuration.
 */
function connect(Config $conf)
{
    $dsns =&amp; $conf-&gt;searchPath(array('config', 'db'));
    if ($dsns === false) {
        throw new Example_Config_Exception(
            'Unable to find config/db section in configuration.'
        );
    }

    $dsns =&amp; $dsns-&gt;toArray();
    
    foreach ($dsns as $dsn) {
        try { 
            $this-&gt;connectDB($dsn);
            return;
        } catch (Example_Datasource_Exception $e) {
            // Some warning/logging code recording the failure
            // to connect to one of the databases
        }
    }
    throw new Example_Datasource_Exception(
        'Unable to connect to any of the configured databases'
    );
}
?&gt;</programlisting>

    <para>This second example shows an exception being caught and recovered
    from. Altough the lower level connectDB method is unable to do anything
    but throw an error when one database connection fails, the upper level
    connect method knows the object can go by with any one of the configured
    databases. Since the error was recovered from, the exception is silenced
    at this level and not rethrown.</para>

    <para>The last example illustrates incomplete recovery:</para>

    <programlisting role="php">&lt;?php
/**
 * loadConfig parses the provided configuration. If the configuration
 * is invalid, it will set the configuration to the default config.
 */
function loadConfig(Config $conf)
{
    try {
        $this-&gt;config = $conf-&gt;parse();
    } catch (Config_Parse_Exception e) {
        // Warn/Log code goes here
        // Perform incomplete recovery
        $this-&gt;config = $this-&gt;defaultConfig;
    }
}
?&gt;</programlisting>

    <para>The recovery produces side effects, so it is considered incomplete.
    However, the program may proceed, so the exception is considered handled,
    and must not be rethrown. As in the previous example, when silencing the
    exception, logging or warning should occur.</para>
  </section>

  <section xml:id="chapters.rfcs.error-handling.signalling">
    <title>Error Signaling in PHP5 PEAR packages</title>

    <para>Error conditions in PEAR packages written for PHP5 must be signaled
    using exceptions. Usage of return codes or return PEAR_Error objects is
    deprecated in favor of exceptions. Naturally, packages providing
    compatibility with PHP4 do not fall under these coding guidelines, and may
    thus use the error handling mechanisms defined in the PHP4 PEAR coding
    guidelines.</para>

    <para>An exception should be thrown whenever an error condition is met,
    according to the definition provided in the previous section. The thrown
    exception should contain enough information to debug the error and quickly
    identify the error cause. Note that, during production runs, no exception
    should reach the end-user, so there is no need for concern about technical
    complexity in the exception error messages.</para>

    <para>The basic PEAR_Exception contains a textual error, describing the
    program state that led to the throw and, optionally, a wrapped lower level
    exception, containing more info on the lower level causes of the
    error.</para>

    <para>The kind of information to be included in the Exception is dependent
    on the error condition. From the point of view of exception throwing,
    there are three classes of error conditions:</para>

    <orderedlist>
      <listitem>
        <para>Errors detected during precondition checks</para>
      </listitem>

      <listitem>
        <para>Lower level library errors signaled via error return codes or
        error return objects.</para>
      </listitem>

      <listitem>
        <para>Uncorrectable lower library exceptions.</para>
      </listitem>
    </orderedlist>

    <para>Errors detected during precondition checks should contain a
    description of the failed check. If possible, the description should
    contain the violating value. Naturally, no wrapped exception can be
    included, as there isn't a lower level cause of the error. Example:</para>

    <programlisting role="php">&lt;?php
function divide($x,$y)
{
    if ($y == 0) {
        throw new Example_Aritmetic_Exception('Divide by zero');
    }
    return $x/$y;
}
?&gt;</programlisting>

    <para>Errors signaled via return codes by lower level libraries, if
    unrecoverable, should be turned into exceptions. The error description
    should try to convey all information contained in the original error. One
    example, is the connect method previously presented:</para>

    <programlisting role="php">&lt;?php
/**
 * Connect to Specified Database
 *
 * @throws Example_Datasource_Exception when it can't connect to specified DSN.
 */
function connectDB($dsn)
{
    $this-&gt;db =&amp; DB::connect($dsn);
    if (DB::isError($this-&gt;db)) {
        throw new Example_Datasource_Exception(
            "Unable to connect to $dsn:" . $this-&gt;db-&gt;getMessage()
        );
    }
}
?&gt;</programlisting>

    <para>Lower library exceptions, if they can't be corrected, should either
    be rethrown or bubbled up. When rethrowing, the original exception must be
    wrapped inside the one being thrown. When letting the exception bubble up,
    the exception just isn't handled and will continue up the call stack in
    search of a handler.</para>

    <para>One example for rethrowing:</para>

    <programlisting role="php">&lt;?php
function preTaxPrice($retailPrice, $taxRate)
{
    try {
        return $this-&gt;divide($retailPrice, 1 + $taxRate);
    } catch (Example_Aritmetic_Exception e) {
        throw new Example_Tax_Exception('Invalid tax rate.', e);
    }
}
?&gt;</programlisting>

    <para>And the same example for bubbling up:</para>

    <programlisting role="php">&lt;?php
function preTaxPrice($retailPrice, $taxRate)
{
    return $this-&gt;divide($retailPrice, 1 + $taxRate);
}
?&gt;</programlisting>

    <para>The case between rethrowing or bubbling up is one of software
    architecture: Exceptions should be bubbled up, except in these two
    cases:</para>

    <orderedlist>
      <listitem>
        <para>The original exception is from another package. Letting it
        bubble up would cause implementation details to be exposed, violating
        layer abstraction, conducing to poor design.</para>
      </listitem>

      <listitem>
        <para>The current method can add useful debugging information to the
        received error before rethrowing.</para>
      </listitem>
    </orderedlist>
  </section>

  <section xml:id="chapters.rfcs.error-handling.flow">
    <title>Exceptions and normal program flow</title>

    <para>Exceptions should never be used as normal program flow. If removing
    all exception handling logic (try-catch statements) from the program, the
    remaining code should represent the "One True Path" -- the flow that would
    be executed in the absence of errors.</para>

    <para>This requirement is equivalent to requiring that exceptions be
    thrown only on error conditions, and never in normal program
    states.</para>

    <para>One example of a method that wrongly uses the bubble up capability
    of exceptions to return a result from a deep recursion:</para>

    <programlisting role="php">&lt;?php

/**
 * Recursively search a tree for string.
 *
 * @throws ResultException
 */
public function search(TreeNode $node, $data)
{
    if ($node-&gt;data === $data) {
         throw new ResultException( $node );
    } else {
         search( $node-&gt;leftChild, $data );
         search( $node-&gt;rightChild, $data );
    }
}
?&gt;</programlisting>

    <para>In the example the ResultException is simply using the "eject!"
    qualities of exception handling to jump out of deeply nested recursion.
    When actually used to signify an error this is a very powerful feature,
    but in the example above this is simply lazy development.</para>
  </section>

  <section xml:id="chapters.rfcs.error-handling.hierarchies">
    <title>Exception class hierarchies</title>

    <para>All of PEAR packages exceptions must be descendant from
    PEAR_Exception. PEAR_Exception provides exception wrapping abilities,
    absent from the top level PHP Exception class, and needed to comply with
    the previous section requirements.</para>

    <para>Aditionally, each PEAR package must provide a top level exception,
    named &lt;Package_Name&gt;_Exception. It is considered best practice that
    the package never throws Exceptions that aren't descendant from its top
    level exception.</para>
  </section>

  <section xml:id="chapters.rfcs.error-handling.documentation">
    <title>Documenting Exceptions</title>

    <para>Because PHP, unlike Java, does not require you to explicitly state
    which Exceptions a method throws in the method signature, it is critical
    that Exceptions be thoroughly documented in your method headers.</para>

    <para>Exceptions should be documented using the <token>@throws</token>
    phpdoc keyword:</para>

    <programlisting role="php">&lt;?php
/**
 * This method searches for aliens.
 *
 * @return array Array of Aliens objects.
 *
 * @throws AntennaBrokenException If the impedence readings indicate
 *                                that the antenna is broken.
 * @throws AntennaInUseException  If another process is using the
 *                                antenna already.
 */
public function findAliens($color = 'green');
?&gt;</programlisting>

    <para>In many cases middle layers of an application will rewrap any
    lower-level exceptions into more meaningful application exceptions. This
    also needs to be made clear:</para>

    <programlisting role="php">&lt;?php
/**
 * Load session objects into shared memory.
 *
 * @throws LoadingException Any lower-level IOException will be wrapped
 *                          and re-thrown as a LoadingException.
 */
public function loadSessionObjects();
?&gt;</programlisting>

    <para>In other cases your method may simply be a conduit through which
    lower level exceptions can pass freely. As challenging as it may be, your
    method should also document which exceptions it is
    <emphasis>not</emphasis> catching.</para>

    <programlisting role="php">&lt;?php
/**
 * Performs a batch of database queries (atomically, not in transaction).
 *
 * @throws SQLException Low-level SQL errors will bubble up through this method.
 */
public function batchExecute();
?&gt;</programlisting>
  </section>

  <section xml:id="chapters.rfcs.error-handling.api">
    <title>Exceptions as part of the API</title>

    <para>Exceptions play a critical role in the API of your library.
    Developers using your library <emphasis>depend</emphasis> on accurate
    descriptions of where and why exceptions might be thrown from your
    package. Documentation is critical. Also maintaining the types of messages
    that are thrown is also an important requirement for maintaining
    backwards-compatibility.</para>

    <para>Because Exceptions are critical to the API of your package, you must
    ensure that you don't break backwards compatibility by making changes to
    exceptions.</para>

    <para>Things that break BC include:</para>

    <itemizedlist>
      <listitem>
        <para>Any change to which methods throw exceptions.</para>
      </listitem>

      <listitem>
        <para>A change whereby a method throws an exception higher in the
        inheritance tree. For example, if you changed your method to throw a
        <literal>PEAR_Exception</literal> rather than a
        <literal>PEAR_IOException</literal>, you would be breaking backwards
        compatibility.</para>
      </listitem>
    </itemizedlist>

    <para>Things that do not break BC:</para>

    <itemizedlist>
      <listitem>
        <para>Throwing a subclass of the original exception. For example,
        changing a method to throw <literal>PEAR_IOException</literal> when
        before it had been throwing <literal>PEAR_Exception</literal> would
        not break BC (provided that <literal>PEAR_IOException</literal>
        extends <literal>PEAR_Exception</literal>).</para>
      </listitem>
    </itemizedlist>
  </section>
</chapter>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.