<?xml version="1.0" encoding="UTF-8"?>
<?xml-model xlink:href="http://www.oasis-open.org/docbook/xml/5.0/rng/docbookxi.rng" 
            schematypens="http://relaxng.org/ns/structure/1.0"?>
<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xi="http://www.w3.org/2001/XInclude"
    xmlns:xlink="http://www.w3.org/1999/xlink" version="5.0" xml:id="ch.extending">
    <title>Extending Phing</title>
    <para>Phing was designed to be flexible and easily extensible. Phing's existing core and
        optional tasks do provide a great deal of flexibility in processing files, performing
        database actions, and even getting user feedback during a build process. In some cases,
        however, the existing tasks just won't suffice and because of Phing's open, modular
        architecture adding exactly the functionality you need is often quite trivial.</para>
    <para>In this chapter we'll look primarily at how to create your own tasks, since that is
        probably the most useful way to extend Phing. We'll also give some more information about
        Phing's design and inner workings.</para>
    <sect1>
        <title> Extension Possibilities </title>
        <para>There are three main areas where Phing can be extended: <xref
                xlink:href="#sect.ext.tasks"/>, <xref xlink:href="#sect.ext.types" xlink:title="Types"/>, <xref
                xlink:href="#sect.ext.mappers" xlink:title="Mappers"/>. The following sections discuss these
            options.</para>
 
        <sect2 xml:id="sect.ext.tasks" xreflabel="Tasks">
            <title> Tasks </title>

            <para>Tasks are pieces of codes that perform an atomic action like installing a file.
                Therefore a special worker class hast to be created and stored in a specific
                location, that actually implements the job. The worker is just the interface to
                Phing that must fulfill some requirements discussed later in this chapter, however
                it can - but not necessarily must - use other classes, workers and libraries that
                aid performing the operations needed.</para>

        </sect2>
        <sect2 xml:id="sect.ext.types" xreflabel="Types">
            <title> Types </title>

            <para>Extending types is a rare need; nevertheless, you can do it. A possible type you
                might implement is <literal>urlset</literal>, for example.</para>
            <para>You may end up needing a new type for a task you write; for example, if you were
                writing the XSLTTask you might discover that you needed a special type for
                XSLTParams (even though in that case you could probably use the generic name/value
                Parameter type). In cases where the type is really only for a single task, you may
                want to just define the type class in the same file as the Task class, rather than
                creating an official stand-alone <literal>Type</literal>.</para>

        </sect2>
        <sect2 xml:id="sect.ext.mappers" xreflabel="Mappers">
            <title> Mappers </title>

            <para>Creating new mappers is also a rare need, since most everything can be handled by
                the <xref xlink:href="#app.coretypes"/>. The Mapper framework does provide a simple
                way for defining your own mappers to use instead, however, and mappers implement a
                very simple interface.</para>
        </sect2>
    </sect1>

    <sect1>
        <title> Source Layout </title>

        <sect2>
            <title> Files And Directories </title>

            <para>Before you are going to start to extend Phing let's have a look at the source
                layout. You should be comfortable with the organization of files witch in the source
                tree of Phing before start coding. After you extracted the source distribution or
                checked it out from <acronym>git</acronym> you should see the following directory
                structure:</para>

            <literallayout xml:id="phing.dirlayout">$PHING_HOME
  |-- bin
  |-- classes
  |    `-- phing
  |         |-- filters
  |         |    `-- util
  |         |-- mappers
  |         |-- parser
  |         |-- tasks
  |         |    |-- ext
  |         |    |-- system
  |         |    |    `-- condition
  |         |    `-- user
  |         `-- types
  |-- docs
  |    `-- phing_guide
  `-- test
       |-- classes
       `-- etc</literallayout>
            <para>The following table briefly describes the contents of the major
                directories:</para>

            <table>
                <title>Phing source tree directories</title>
                <tgroup cols="2">
                    <colspec colname="name" colnum="1" colwidth="1*"/>
                    <colspec colname="description" colnum="2" colwidth="2*"/>
                    <thead>
                        <row>
                            <entry>Directory</entry>
                            <entry>Contents</entry>
                        </row>
                    </thead>
                    <tbody>
                        <row>
                            <entry><para><filename role="dir">bin</filename></para></entry>
                            <entry>
                                <para>The basic applications (phing, configure) as well as the
                                    wrapper scripts for different platforms (currently Unix and
                                    Windows).</para>
                            </entry>
                        </row>
                        <row>
                            <entry><para><filename role="dir">classes</filename></para></entry>
                            <entry><para>Repository of all the classes used by Phing. This is the
                                    base directory that should be on the PHP include_path. In this
                                    directory you will find the subdirectory phing/ with all the
                                    Phing relevant classes.</para></entry>
                        </row>
                        <row>
                            <entry><para><filename role="dir">docs</filename></para></entry>
                            <entry>
                                <para>Documentation files. Generated books, online manuals as well
                                    as the PHPDoc generated API documentation.</para>
                            </entry>
                        </row>
                        <row>
                            <entry><para><filename role="dir">test</filename></para></entry>
                            <entry>
                                <para>A set of testcases for different tasks, mappers and types. If
                                    you are developing in <acronym>git</acronym> you should add a
                                    testcase for each implementation you check in.</para>
                            </entry>
                        </row>
                    </tbody>
                </tgroup>
            </table>

            <para>Currently there is no distinction between the <literal>source</literal> layout and
                the <literal>build</literal> layout of Phing. The <link
                    xlink:href="#phing.dirlayout">directory layout</link> shows the file tree that
                carries some additional files like the Phing website. Later on there may be a
                buildfile to create a clean distribution tree of Phing itself.</para>

        </sect2>
        <sect2>
            <title> File Naming Conventions </title>

            <para>There are some file naming conventions used by Phing. Here's a quick rundown on
                the most basic conventions. A more detailed list can be found in [See Naming And
                Coding Standards]:</para>

            <itemizedlist>
                <listitem>
                    <para>Filenames consist of no more or less than two elements:
                            <literal>name</literal> and <literal>extension</literal> .</para>
                </listitem>
                <listitem>
                    <para>Choose short descriptive filenames (must be less than 31 chars)</para>
                </listitem>
                <listitem>
                    <para>Names must not contain dots.</para>
                </listitem>
                <listitem>
                    <para>Files containing PHP code must end with the extension
                            <literal>.php</literal> .</para>
                </listitem>
                <listitem>
                    <para>There must be only one class per file (no procedural methods allowed, use
                        a separate file for them), with the exception of "inner"-type / helper
                        classes that can be declared in the same file as the "outer" / main
                        class.</para>
                </listitem>
                <listitem>
                    <para>The name portion of the file must be named exactly like the class it
                        contains.</para>
                </listitem>
                <listitem>
                    <para>Buildfiles and configure rulesets must end with the extension
                            <literal>.xml</literal> .</para>
                </listitem>
            </itemizedlist>

        </sect2>
        <sect2>
            <title> Coding Standards </title>

            <para>We are using PEAR coding standards. We are using a less strict version of these
                standards, but we do insist that new contributions have phpdoc comments and make
                explicitly declarations about public/protected/private variables and methods. If you
                have suggestions about improvements to Phing codebase, don't hesitate to let us
                know.</para>
        </sect2>
    </sect1>

    <sect1>
        <title> System Initialization </title>

        <para>PHP installations are typically quite customized -- e.g. different memory_limit,
            execution timeout values, etc. The first thing that Phing does is modify PHP INI
            variables to create a standard PHP environment. This is performed by the <literal>init
                layer</literal> of Phing that uses a three-level initialization procedure. It
            basically consists of three different files: </para>

        <itemizedlist>
            <listitem>
                <para>Platform specific wrapper scripts in bin/</para>
            </listitem>
            <listitem>
                <para>Main application in bin/</para>
            </listitem>
            <listitem>
                <para>Phing class in classes/phing/</para>
            </listitem>
        </itemizedlist>

        <para>At the first look this may seem to be unnecessary overhead. Why three levels of
            initialization? The main reason why there are several entry points is that Phing is
            build so that other frontends (e.g. PHP-GTK) could be used in place of the command
            line.</para>

        <sect2>
            <title> Wrapper Scripts </title>

            <para>This scripts are technical not required but provided for the ease of use. Imagine
                you have to type every time you want to build your project:</para>

            <screen>php -qC /path/to/phing/bin/phing.php -verbose all distro snapshot</screen>

            <para>Indeed that is not very elegant. Furthermore if you are lax in setting your
                environment variables these script can guess the proper variables for you. However
                you should always set them.</para>

            <para> The scripts are platform dependent, so you will find shell scripts for
                    <literal>Unix</literal> like platforms (sh) as well as the batch scripts for
                    <literal>Windows</literal> platforms. If you set-up your path properly you can
                call Phing everywhere in your system with this command-line (referring to the above
                example): </para>

            <screen>phing -v2 all distro</screen>

        </sect2>
        <sect2>
            <title> The Main Application (phing.php) </title>

            <para>This is basically a wrapper for the Phing class that actually does all the logic
                for you. If you look at the source code for phing.php you will see that all real
                initialization is handled in the Phing class. phing.php is simply the command line
                entry point for Phing.</para>

        </sect2>
        <sect2>
            <title> The Phing Class </title>

            <para>Given that all the prior initialization steps passed successfully the Phing is
                included and <literal>Phing::startup()</literal> is invoked by the main application
                script. It sets-up the system components, system constants ini-settings, PEAR and
                some other stuff. The detailed start-up process is as follows: </para>

            <itemizedlist>
                <listitem>
                    <para>Start Timer</para>
                </listitem>
                <listitem>
                    <para>Set System Constants</para>
                </listitem>
                <listitem>
                    <para>Set Ini-Settings</para>
                </listitem>
                <listitem>
                    <para>Set Include Paths</para>
                </listitem>
            </itemizedlist>

            <para>After the main application completed all operations (successfully or
                unsuccessfully) it calls <literal>Phing::shutdown(EXIT_CODE)</literal> that takes
                care of a proper destruction of all objects and a gracefully termination of the
                program by returning an <literal>exit code</literal> for shell usage (see [See
                Program Exit Codes] for a list of exit codes).</para>
        </sect2>
    </sect1>

    <sect1>
        <title> System Services </title>

        <sect2>
            <title> The Exception system </title>
            <para>Phing uses the PHP5 try/catch/throw Exception system. Phing defines a number of
                Exception subclasses for more fine-grained handling of Exceptions. Low level
                Exceptions that cannot be handled will be wrapped in a
                    <literal>BuildException</literal> and caught by the outer-most catch() {}
                block.</para>
        </sect2>
    </sect1>

    <sect1>
        <title> Build Lifecycle </title>
        <para>This section exists to explain -- or try -- how Phing "works". Particularly, how Phing
            proceeds through a build file and invokes tasks and types based on the tags that it
            encounters.</para>
        <sect2>
            <title> How Phing Parses Buildfiles </title>
            <para>Phing uses an ExpatParser class and PHP's native expat XML functions to handle the
                parsing of build files. The handler classes all extend the
                phing.parser.AbstractHandler class. These handler classes "handle" the tags that are
                found in the buildfile.</para>
            <para>Core tasks and datatypes are mapped to XML tag names in the defaults.properties
                files -- specifically phing/tasks/defaults.properties and
                phing/types/defaults.properties.</para>
            <para>It works roughly like this:</para>
            <orderedlist>
                <listitem>
                    <para><literal>phing.parser.RootHandler</literal> is registered to handle the
                        buildfile XML document</para>
                </listitem>
                <listitem>
                    <para>RootHanlder expects to find exactly one element:
                            <literal>&lt;project></literal>. RootHandler invokes the ProjectHandler
                        with the attributes from the &lt;project> tag or throws an exception if no
                        &lt;project> is found, or if something else is found instead.</para>
                </listitem>
                <listitem>
                    <para><literal>ProjectHandler</literal> expects to find
                            <literal>&lt;target></literal> tags; for these
                            <literal>ProjectHandler</literal> invokes the
                            <literal>TargetHandler</literal>. ProjectHandler also has exceptions for
                        handling certain tasks that can be performed at the top-level:
                            <literal>&lt;resolve></literal>, <literal>&lt;taskdef></literal>,
                            <literal>&lt;typedef></literal>, and <literal>&lt;property></literal>;
                        for these <literal>ProjectHandler</literal> invokes the TaskHandler class.
                        If a tag is presented that doesn't match any expected tags, then
                            <literal>ProjectHandler</literal> assumes it is a datatype and invokes
                        the <literal>DataTypeHandler</literal>.</para>
                </listitem>
                <listitem>
                    <para><literal>TargetHandler</literal> expects all tags to be either tasks or
                        datatypes and invokes the appropriate handler (based on the mappings
                        provided in the <filename>defaults.properties</filename> files).</para>
                </listitem>
                <listitem>
                    <para>Tasks and datatypes can have nested elements, but only if they correspond
                        to a create*() method in the task or datatype class. E.g. a nested
                            <literal>&lt;param></literal> tag must correspond to a
                            <literal>createParam()</literal> method of the task or datatype.</para>
                </listitem>
            </orderedlist>
            <para>
                <literal>... More to come ...</literal>
            </para>
        </sect2>
    </sect1>

    <sect1>
        <title> Writing Tasks </title>

        <sect2>
            <title> Creating A Task </title>

            <para>We will start creating a rather simple task which basically does nothing more than
                echo a message to the screen. See [below] for the source code and the following
                [below] for the XML definition that is used for this task.</para>


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

require_once "phing/Task.php";

class MyEchoTask extends Task {

    /**
     * The message passed in the buildfile.
     */
    private $message = null;

    /**
     * The setter for the attribute "message"
     */
    public function setMessage($str) {
        $this->message = $str;
    }

    /**
     * The init method: Do init steps.
     */
    public function init() {
      // nothing to do here
    }

    /**
     * The main entry point method.
     */
    public function main() {
      print($this->message);
    }
}

?></programlisting>

            <para>This code contains a rather simple, but complete Phing task. It is assumed that
                the file is named <literal>MyEchoTask.php</literal> and placed in
                    <literal>classes/phing/tasks/my</literal> directory. We'll explain the source
                code in detail shortly. But first we'd like to discuss how we should register the
                task to Phing so that it can be executed during the build process.</para>

        </sect2>
        <sect2>
            <title> Using the Task </title>

            <para>The task shown [above] must somehow get called by Phing. Therefore it must be made
                available to Phing so that the buildfile parser is aware a correlating XML element
                and it's parameters. Have a look at the minimalistic buildfile example given in [the
                buildfile below] that does exactly this.</para>


            <programlisting language="xml">&lt;?xml version="1.0" ?>

&lt;project name="test" basedir="." default="test.myecho">
    &lt;taskdef name="myecho" classname="phing.tasks.my.MyEchoTask" />

    &lt;target name="test.myecho">
      &lt;myecho message="Hello World" />
    &lt;/target>
&lt;/project></programlisting>

            <para> Besides the XML document prolog and the shell elements that are required to
                properly execute the task (project, target) you'll find the
                    <literal>&lt;taskdef></literal> element (line 4) that properly registers your
                custom task to Phing. For a detailed synopsis of the taskdef element see the
                [description of this task].</para>

            <para> Now, as we have registered the task by assigning a name and the worker class
                ([see source code above]) it is ready for usage within the
                    <literal>&lt;target></literal> context (line 8). You see that we pass the
                message that our task should echo to the screen via an XML attribute called
                "message".</para>

        </sect2>
        <sect2>
            <title>Source Discussion </title>

            <para>No that you've got the knowledge to execute the task in a buildfile it's time to
                discuss how everything works.</para>

        </sect2>
        <sect2>
            <title>Task Structure </title>

            <para>All files containing the definition of a task class follow a common well formed
                structure:</para>

            <itemizedlist>
                <listitem>
                    <para>Include/require statements to import all required classes</para>
                </listitem>
                <listitem>
                    <para>The class declaration and definition</para>
                </listitem>
                <listitem>
                    <para>The class's properties</para>
                </listitem>
                <listitem>
                    <para>The class's constructor</para>
                </listitem>
                <listitem>
                    <para>Setter methods for each XML attribute</para>
                </listitem>
                <listitem>
                    <para>The <literal>init()</literal> method</para>
                </listitem>
                <listitem>
                    <para>The <literal>main()</literal> method</para>
                </listitem>
                <listitem>
                    <para>Arbitrary <literal>private</literal> (or
                            <literal>protected</literal>)<literal> </literal>class methods</para>
                </listitem>
            </itemizedlist>

        </sect2>
        <sect2>
            <title>Includes </title>

            <para>Always include/require all the classes needed for this task in full written
                notation. Furthermore you should always include <literal>phing/Task.php</literal> at
                the very top of your include block. Then include all other required system or
                proprietary classes.</para>

        </sect2>
        <sect2>
            <title> Class Declaration </title>

            <para>If you look at line 5 in [the source code of the task] you will find the
                    <literal>class declaration</literal>. This will be familiar to you if you are
                experienced with OOP in PHP (we assume here that you are). Furthermore there are
                some fine-grained rules you must obey when creating the classes (see also,[naming
                and coding standards]):</para>

            <itemizedlist>
                <listitem>
                    <para>Your classname must be exactly like the taskname you are going to
                        implement plus the suffix "Task". In our example case the classname is
                            <literal>MyEchoTask</literal> (constructed by the taskname
                            "<literal>myecho</literal>" plus the suffix "<literal>task</literal>").
                        The upper/lower case casing is currently only for better reading. However,
                        it is encouraged that you use it this way.</para>
                </listitem>
                <listitem>
                    <para>The task class you are creating must at least extend
                            "<literal>Task</literal>" to inherit all task specific methods.</para>
                </listitem>
            </itemizedlist>

        </sect2>
        <sect2>
            <title> Class Properties </title>

            <para>The next lines you are coding are class properties. Most of them are inherited
                from the Task superclass, so there's not need to redeclare them. Nevertheless you
                should <literal>declare</literal> the following ones by your own: </para>

            <itemizedlist>
                <listitem>
                    <para>Taskname. Always hard code the <literal>taskname</literal> property that
                        equals the name of the XML element that your task claims. Currently this
                        information is not used - but it will be in the future.</para>
                </listitem>
                <listitem>
                    <para>Your arbitrary properties that reflect the XML attributes/elements which
                        your task accepts.</para>
                </listitem>
            </itemizedlist>

            <para>In the <literal>MyEchoTask</literal> example the coded properties can be found in
                lines 7 to 11. Give you properties meaningful descriptive names that clearly state
                their function within the context. A couple of properties are inherited from the
                superclass that must not be declared in the properties part of the code.</para>

            <para>For a list of inherited properties (most of them are reserved, so be sure not to
                overwrite them with your own) can be found in the "Phing API Reference" in the
                    <filename role="dir">docs/api/</filename> directory.</para>

        </sect2>
        <sect2>
            <title> The Constructor </title>

            <para> The next block that follows is the class's constructor. It must be present and
                call at least the constructor or the parent class. Of course, you can add some
                initialization data here. It is recommended that you <literal>define</literal> your
                prior declared properties here.</para>

        </sect2>
        <sect2>
            <title> Setter Methods </title>

            <para> As you can see in the XML definition of our task ([see buildfile above] , line 9)
                there is an attribute defined with the task itself, namely "message" with a value of
                the the text string that our task should echo. The task must somehow become aware of
                the attribute name and the value. Therefore the <literal>setter methods</literal>
                exist.</para>

            <para>For each attribute you want to import to the task's namespace you have to define a
                method named exactly after the very attribute plus the string "set" prepended. This
                method accepts exactly one parameter that holds the value of the attribute. Now you
                can set the a class internal property to the value that is passed via the setter
                method.</para>

            <para> In the setter method you should also perform any casting operations and/or check
                if the attribute value is a valid value. If this is not the case, throw a
                    <literal>BuildException</literal>. In some cases, such as when you have three
                attributes and at least one of them should be set, you may want to check the
                attribute values inside the init() or main() method.</para>

            <para> In out example the setter is named <literal>setMessage</literal> , because the
                XML attribute the echo task accepts is "message". setMessage now takes the string
                "Hello World" provided by the parser and sets the value of the internal class
                property <literal>$strMessage</literal> to "Hello World". It is now available to the
                task for further disposal.</para>

        </sect2>
        <sect2>
            <title> Creator Methods </title>

            <para>Creator methods allow you to manage nested XML tags in your new Phing Task.</para>

        </sect2>
        <sect2>
            <title>
                <literal>init()</literal> Method </title>

            <para>The <literal>init</literal> method gets called when the
                    <literal>&lt;taskname></literal> xml element closes. It must be implemented even
                if it does nothing like in the example above. You can do init steps here required to
                setup your task object properly. After calling the Init-Method the task object
                remains untouched by the parser. Init should not perform operations related somehow
                to the action the task performs. An example of using init may be cleaning up the
                $strMessage variable in our example (i.e. <literal>trim($strMessage)</literal>) or
                importing additional workers needed for this task.</para>

            <para>The init method should return true or an error object evaluated by the governing
                logic. If you don't implement init method, phing will shout down with a fatal
                error.</para>

        </sect2>
        <sect2>
            <title>
                <literal>main()</literal> Method </title>

            <para> There is exactly one entry entry point to execute the task. It is called after
                the complete buildfile has been parsed and all targets and tasks have been scheduled
                for execution. From this point forward the very implementation of the tasks action
                starts. In case of our example a message (imported by the proper setter method) is
                Logged to the screen through the system's "Logger" service (the very action this
                task is written for). The <literal>Log()</literal> method-call in this case accepts
                two parameters: a event constant and the message to log.</para>

        </sect2>
        <sect2>
            <title> Arbitrary Methods </title>

            <para>For the more or less simple cases (as our example) all the logic of the task is
                coded in the Main() method. However for more complex tasks common sense dictates
                that particular action should be swapped to smaller, logically contained units of
                code. The most common way to do this is separating logic into private class methods
                - and in even more complex tasks in separate libraries.</para>

            <programlisting language="php">private function myPrivateMethod() {
    // definition
}</programlisting>
        </sect2>
    </sect1>

    <sect1>
        <title> Writing Types </title>

        <para>You should only create a standalone Type if the Type needs to be shared by more than
            one Task. If the Type is only needed for a specific Task -- for example to handle a
            special parameter or other tag needed for that Task -- then the Type class should just
            be defined within the same file as the Task. (For example,
                <filename>phing/filters/XSLTFilter.php</filename> also includes an
                <literal>XSLTParam</literal> class that is not used anywhere else.) </para>
        <para>For cases where you do need a more generic Type defined, you can create your own Type
            class -- similar to the way a Task is created.</para>
        <sect2>
            <title>Creating a DataType</title>
            <para>Type classes need to extend the abstract DataType class. Besides providing a means
                of categorizing types, the DataType class provides the methods necessary to support
                the "<literal>refid</literal>" attribute. (All types can be given an id, and can be
                referred to later using that id.)</para>
            <para>In this example we are creating a DSN type because we have written a number of
                DB-related Tasks, each of which need to know how to connect to the database; instead
                of having database parameters for each task, we've created a DSN type so that we can
                identify the connection parameters once and then use it in all our db Tasks.</para>
            <programlisting language="php">require_once "phing/types/DataType.php";

/**
 * This Type represents a DB Connection.
 */
class DSN extends DataType {

  private $url;
  private $username;
  private $password;
  private $persistent = false;

  /**
   * Sets the URL part: mysql://localhost/mydatabase
   */
  public function setUrl($url) {
    $this->url = $url;
  }

  /**
   * Sets username to use in connection.
   */
  public function setUsername($username) {
    $this->username = $username;
  }

  /**
   * Sets password to use in connection.
   */
  public function setPassword($password) {
    $this->password = $password;
  }

  /**
   * Set whether to use persistent connection.
   * @param boolean $persist
   */
  public function setPersistent($persist) {
    $this->persistent = (boolean) $persist;
  }

  public function getUrl(Project $p) {
    if ($this->isReference()) {
      return $this->getRef($p)->getUrl($p);
    }
    return $this->url;
  }

  public function getUsername(Project $p) {
    if ($this->isReference()) {
      return $this->getRef($p)->getUsername($p);
    }
    return $this->username;
  }

  public function getPassword(Project $p) {
    if ($this->isReference()) {
      return $this->getRef($p)->getPassword($p);
    }
    return $this->password;
  }

  public function getPersistent(Project $p) {
    if ($this->isReference()) {
      return $this->getRef($p)->getPersistent($p);
    }
    return $this->persistent;
  }

  /**
   * Gets a combined hash/array for DSN as used by PEAR.
   * @return array
   */
  public function getPEARDSN(Project $p) {
    if ($this->isReference()) {
      return $this->getRef($p)->getPEARDSN($p);
    }

    include_once 'DB.php';
    $dsninfo = DB::parseDSN($this->url);
    $dsninfo['username'] = $this->username;
    $dsninfo['password'] = $this->password;
    $dsninfo['persistent'] = $this->persistent;

    return $dsninfo;
  }

  /**
   * Your datatype must implement this function, which ensures that there
   * are no circular references and that the reference is of the correct
   * type (DSN in this example).
   *
   * @return DSN
   */
  public function getRef(Project $p) {
    if ( !$this->checked ) {
      $stk = array();
      array_push($stk, $this);
      $this->dieOnCircularReference($stk, $p);
    }
    $o = $this->ref->getReferencedObject($p);
    if ( !($o instanceof DSN) ) {
      throw new BuildException($this->ref->getRefId()." doesn't denote a DSN");
    } else {
      return $o;
    }
  }

}</programlisting>
        </sect2>
        <sect2>
            <title> Using the DataType </title>
            <para>The <literal>TypedefTask</literal> provides a way to "declare" your type so that
                you can use it in your build file. Here is how you would use this type in order to
                define a single DSN and use it for multiple tasks. (Of course you could specify the
                DSN connection parameters each time, but the premise behind needing a DSN datatype
                was to avoid specifying the connection parameters for each task.) </para>

            <programlisting language="xml">&lt;?xml version="1.0" ?>

&lt;project name="test" basedir=".">

  &lt;typedef name="dsn" classname="myapp.types.DSN" />

  &lt;dsn
      id="maindsn"
      url="mysql://localhost/mydatabase"
      username="root"
      password=""
      persistent="false" />

  &lt;target name="main">

    &lt;my-special-db-task>
	     &lt;dsn refid="maindsn"/>
    &lt;/my-special-db-task>

    &lt;my-other-db-task>
      &lt;dsn refid="maindsn"/>
    &lt;/my-other-db-task>

  &lt;/target>

&lt;/project></programlisting>

        </sect2>
        <sect2>
            <title> Source Discussion </title>
            <sect3>
                <title>Getters &amp; Setters</title>
                <para>You must provide a setter method for every attribute you want to set from the
                    XML build file. It is good practice to also provide a getter method, but in
                    practice you can decide how your tasks will use your task. In the example above,
                    we've provided a getter method for each attribute and we've also provided an
                    additional method:<literal>DSN::getPEARDSN()</literal> which returns the DSN
                    hash array used by <literal>PEAR::DB</literal>, <literal>PEAR::MDB</literal>,
                    and Creole. Depending on the needs of the Tasks using this DataType, we may only
                    wish to provide the <literal>getPEARDSN()</literal> method rather than a getter
                    for each attribute.</para>
                <para>Also important to note is that the getter method needs to check to see whether
                    the current DataType is a reference to a previously defined DataType -- the
                        <literal>DataType::isReference()</literal> exists for this purpose. For this
                    reason, the getter methods need to be called with the current project, because
                    References are stored relative to a project.</para>
            </sect3>
            <sect3>
                <title>The getRef() Method</title>
                <para>The <literal>getRef()</literal> task needs to be implemented in your Type.
                    This method is responsible for returning a referenced object; it needs to check
                    to make sure the referenced object is of the correct type (i.e. you can't try to
                    refer to a RegularExpresson from a DSN DataType) and that the reference is not
                    circular.</para>
                <para>You can probably just copy this method from an existing Type and make the few
                    changes that customize it to your Type.</para>
            </sect3>
        </sect2>
    </sect1>

    <sect1>
        <title> Writing Mappers </title>
        <para>Writing your own filename mapper classes will allow you to control how names are
            transformed in tasks like <literal>CopyTask</literal>, <literal>MoveTask</literal>,
                <literal>XSLTTask</literal>, etc. In some cases you may want to extend existing
            mappers (e.g. creating a GlobMapper that also transforms to uppercase); in other cases,
            you may simply want to create a very specific name transformation that isn't easily
            accomplished with other mappers like <literal>GlobMapper</literal> or
                <literal>RegexpMapper</literal>.</para>
        <sect2>
            <title> Creating a Mapper </title>
            <para>Writing filename mappers is simplified by interface support in PHP5. Essentially,
                your custom filename mapper must implement
                    <literal>phing.mappers.FileNameMapper</literal>. Here's an example of a filename
                mapper that creates DOS-style file names. For this example, the "to" and "from"
                attributes are not needed because all files will be transformed. To see the "to" and
                "from" attributes in action, look at <literal>phing.mappers.GlobMapper</literal> or
                    <literal>phing.mappers.RegexpMapper</literal>.</para>
            <programlisting language="php">require_once "phing/mappers/FileNameMapper.php";

/**
 * A mapper that makes those ugly DOS filenames.
 */
class DOSMapper implements FileNameMapper {

  /**
   * The main() method actually performs the mapping.
   *
   * In this case we transform the $sourceFilename into
   * a DOS-compatible name.  E.g.
   * ExtendingPhing.html -> EXTENDI~.DOC
   *
   * @param string $sourceFilename The name to be coverted.
   * @return array The matched filenames.
   */
  public function main($sourceFilename) {

    $info = pathinfo($sourceFilename);
    $ext = $info['extension'];
    // get basename w/o extension
    $bname = preg_replace('/\.\w+\$/', '', $info['basename']);

    if (strlen($bname) > 8) {
      $bname = substr($bname,0,7) . '~';
    }

    if (strlen($ext) > 3) {
      $ext = substr($bname,0,3);
    }

    if (!empty($ext)) {
      $res = $bname . '.' . $ext;
    } else {
      $res = $bname;
    }

    return (array) strtoupper($res);
  }

  /**
   * The "from" attribute is not needed here, but method must exist.
   */
  public function setFrom($from) {}

	 /**
   * The "from" attribute is not needed here, but method must exist.
   */
  public function setTo($to) {}

}</programlisting>
        </sect2>
        <sect2>
            <title> Using the Mapper </title>
            <para>Assuming that this mapper is saved to <literal>myapp/mappers/DOSMapper.php
                </literal>(relative to a path on PHP's <literal>include_path</literal> or in
                    <literal>PHP_CLASSPATH</literal> env variable), then you would refer to it like
                this in your build file: </para>
            <programlisting language="xml">&lt;mapper classname="myapp.mappers.DOSMapper"/></programlisting>
        </sect2>
    </sect1>

</chapter>
