Extension.php #1

  • //
  • guest/
  • thomas_gray/
  • jambox/
  • main/
  • swarm/
  • library/
  • P4/
  • Connection/
  • Extension.php
  • View
  • Commits
  • Open Download .zip Download (15 KB)
<?php
/**
 * P4PHP Perforce connection implementation.
 *
 * This client implementation provides access to the P4PHP extension in a way
 * that conforms to P4\Connection\ConnectionInterface. This allows the P4PHP extension
 * and the Perforce Command-Line Client wrapper to be used interchangeably.
 *
 * @copyright   2011 Perforce Software. All rights reserved.
 * @license     Please see LICENSE.txt in top-level folder of this distribution.
 * @version     <release>/<patch>
 */

namespace P4\Connection;

use P4;
use P4\Connection\CommandResult;

class Extension extends AbstractConnection
{
    protected $instance;

    /**
     * Constructs a P4 connection object.
     *
     * @param   string  $port        optional - the port to connect to.
     * @param   string  $user        optional - the user to connect as.
     * @param   string  $client      optional - the client spec to use.
     * @param   string  $password    optional - the password to use.
     * @param   string  $ticket      optional - a ticket to use.
     * @throws  P4\Exception         if P4PHP is not loaded
     */
    public function __construct(
        $port = null,
        $user = null,
        $client = null,
        $password = null,
        $ticket = null
    ) {
        // ensure that p4-php is installed.
        if (!extension_loaded('perforce')) {
            throw new P4\Exception(
                'Cannot create P4 API extension instance. Perforce extension not loaded.'
            );
        }

        // create an instance of p4-php.
        $this->instance = new P4;

        // disable automatic sequence expansion (call expandSequences on result object if desired)
        $this->instance->expand_sequences = false;

        // prevent command exceptions from being thrown by P4.
        // we throw our own so that we can attach the result.
        $this->instance->exception_level = 0;

        parent::__construct($port, $user, $client, $password, $ticket);
    }

    /**
     * Disconnect from the Perforce Server.
     *
     * @return  ConnectionInterface     provides fluent interface.
     */
    public function disconnect()
    {
        // call parent to run disconnect callbacks.
        parent::disconnect();

        if ($this->isConnected()) {
            $this->instance->disconnect();
        }

        return $this;
    }

    /**
     * Check connected state.
     *
     * @return  bool    true if connected, false otherwise.
     */
    public function isConnected()
    {
        return $this->instance->connected();
    }

    /**
     * Extends parent to set our instance's password to the returned
     * ticket value if login succeeds.
     *
     * @param   bool|null       $all    get a ticket valid for all hosts (false by default)
     * @return  string|null     the ticket issued by the server or null if
     *                          no ticket issued (ie. user has no password).
     * @throws  Exception\LoginException    if login fails.
     */
    public function login($all = false)
    {
        $ticket = parent::login($all);

        if ($ticket) {
            $this->instance->password = $ticket;
        }

        return $ticket;
    }

    /**
     * Extend set port to update p4-php.
     *
     * @param   string      $port       the port to connect to.
     * @return  ConnectionInterface     provides fluent interface.
     */
    public function setPort($port)
    {
        parent::setPort($port);
        $this->instance->port = $this->getPort();

        return $this;
    }

    /**
     * Extend set user to update p4-php.
     *
     * @param   string      $user       the user to connect as.
     * @return  ConnectionInterface     provides fluent interface.
     */
    public function setUser($user)
    {
        parent::setUser($user);
        $this->instance->user = $this->getUser();

        return $this;
    }

    /**
     * Extend set client to update p4-php.
     *
     * @param   string      $client     the name of the client workspace to use.
     * @return  ConnectionInterface     provides fluent interface.
     */
    public function setClient($client)
    {
        parent::setClient($client);

        // if no client is specified, normally the host name is used.
        // this can collide with an existing depot or client name, so
        // we use a temp id to avoid errors.
        $this->instance->client = $this->getClient() ?: P4\Spec\Client::makeTempId();

        return $this;
    }

    /**
     * Extend set password to update p4-php.
     *
     * @param   string  $password       the password to use as authentication.
     * @return  ConnectionInterface     provides fluent interface.
     */
    public function setPassword($password)
    {
        parent::setPassword($password);
        $this->instance->password = $this->getPassword();

        return $this;
    }

    /**
     * Extend set ticket to update p4-php.
     * Note: the ticket is stored in the password field in p4-php.
     *
     * @param   string  $ticket         the ticket to use as authentication.
     * @return  ConnectionInterface     provides fluent interface.
     */
    public function setTicket($ticket)
    {
        parent::setTicket($ticket);
        if ($ticket) {
            $this->instance->password = $this->getTicket();
        }

        return $this;
    }

    /**
     * Extended to set charset in p4-php.
     * Sets the character set to use for this perforce connection.
     *
     * You should only set a character set when connecting to a
     * 'unicode enabled' server, or when setting the special value
     * of 'none'.
     *
     * @param   string  $charset        the charset to use (e.g. 'utf8').
     * @return  ConnectionInterface     provides fluent interface.
     */
    public function setCharset($charset)
    {
        $this->instance->charset = $charset;

        return parent::setCharset($charset);
    }

    /**
     * Extended to set host name in p4-php.
     * Sets the client host name overriding the environment.
     *
     * @param   string|null $host       the host name to use.
     * @return  ConnectionInterface     provides fluent interface.
     */
    public function setHost($host)
    {
        $this->instance->host = $host;

        return parent::setHost($host);
    }

    /**
     * Extended to set app name in p4-php.
     * Set the name of the application that is using this connection.
     *
     * @param   string|null     $name   the app name to report to the server.
     * @return  ConnectionInterface     provides fluent interface.
     */
    public function setAppName($name)
    {
        $this->instance->set_protocol('app', (string) $name);

        return parent::setAppName($name);
    }

    /**
     * Extended to set program name in p4-php.
     * Set the name of the program that is using this connection.
     *
     * @param   string|null     $name   the program name to report to the server.
     * @return  ConnectionInterface     provides fluent interface.
     */
    public function setProgName($name)
    {
        $this->instance->prog = (string) $name;

        return parent::setProgName($name);
    }

    /**
     * Extended to set program version in p4-php.
     * Set the version of the program that is using this connection.
     *
     * @param   string|null     $version the program version to report to the server.
     * @return  ConnectionInterface      provides fluent interface.
     */
    public function setProgVersion($version)
    {
        $this->instance->version = (string) $version;

        return parent::setProgVersion($version);
    }

    /**
     * Get the identity of this Connection implementation.
     *
     * Resulting array will contain:
     *  - name
     *  - platform
     *  - version    (p4-php version)
     *  - build      (p4-php build)
     *  - apiversion (p4-api version)
     *  - apibuild   (p4-api build)
     *  - date
     *  - original   (all text following 'Rev. ' from original response)
     *
     * @return  array           an array of client Connection information
     * @throws  P4\Exception    if the returned version string is invalid
     */
    public function getConnectionIdentity()
    {
        // obtain the extension's identification
        $output = $this->instance->identify();

        // extract the version string and split into components
        preg_match('/\nRev. (.*)\.$/', $output, $matches);
        $parts = isset($matches[1]) ? preg_split('/\/| \(| API\) \(|\)/', $matches[1]) : null;
        if (count($parts) < 8) {
            $message = 'p4php returned an invalid version string';
            throw new P4\Exception($message);
        }

        // build identity array of version components, including original string
        $identity = array(
            'name'       => $parts[0],
            'platform'   => $parts[1],
            'version'    => $parts[2],
            'build'      => $parts[3],
            'apiversion' => $parts[4],
            'apibuild'   => $parts[5],
            'date'       => $parts[6] . '/' . $parts[7] . '/' . $parts[8],
            'original'   => $matches[1]
        );

        return $identity;
    }

    /**
     * Runs the specified command using the passed output handler.
     * Ensures the output handler is turned back off at completion.
     *
     * If the handler has a 'reset' method it will be called. This is intended
     * to give the handler an opportunity to prepare itself for a fresh run.
     *
     * @param   P4_OutputHandlerAbstract    $handler        the output handler to use
     * @param   string                      $command        the command to run.
     * @param   array|string                $params         optional - one or more arguments.
     * @param   array|string                $input          optional - input for the command - should be provided
     *                                                      in array form when writing perforce spec records.
     * @param   boolean                     $tagged         optional - true/false to enable/disable tagged output.
     *                                                      defaults to true.
     * @param   boolean                     $ignoreErrors   optional - true/false to ignore errors - default false
     * @return  P4\Connection\CommandResult the perforce result object.
     * @throws  \Exception    if the command produced an exception
     */
    public function runHandler(
        $handler,
        $command,
        $params = array(),
        $input = null,
        $tagged = true,
        $ignoreErrors = false
    ) {
        // if the handler has a 'reset' method call it to ensure its ready
        if (method_exists($handler, 'reset')) {
            $handler->reset();
        }

        // set the handler and run our command.
        $this->instance->handler = $handler;
        try {
            $result = $this->run($command, $params, $input, $tagged, $ignoreErrors);
        } catch (\Exception $e) {
            // just catch the exception for now; we'll rethrow later
        }

        // if the handler 'cancelled' the command, there is a chance the connection has been severed
        // we run a test command to check the connection and disconnect if that fails
        // there are a number of oddities to be aware of:
        // - the handler might not have a wasCancelled() method, in which case we assume it cancelled
        // - running any command on a severed connection produces no errors/exceptions/data (bug in P4PHP)
        // - we run 'p4 help' because it locks no tables, produces no errors and has modest output
        // - by explicitly disconnecting any future commands will automatically reconnect
        if ((!method_exists($handler, 'wasCancelled') || $handler->wasCancelled())
            && !$this->doRun('help')->hasData()
        ) {
            $this->disconnect();
        }

        $this->instance->handler = null;

        // if an exception occurred rethrow it now that we've cleared the handler
        if (isset($e)) {
            throw $e;
        }

        return $result;
    }

    /**
     * Actually issues a command. Called by run() to perform the dirty work.
     *
     * @param   string          $command    the command to run.
     * @param   array           $params     optional - arguments.
     * @param   array|string    $input      optional - input for the command - should be provided
     *                                      in array form when writing perforce spec records.
     * @param   boolean         $tagged     optional - true/false to enable/disable tagged output.
     *                                      defaults to true.
     * @return  CommandResult   the perforce result object.
     */
    protected function doRun($command, $params = array(), $input = null, $tagged = true)
    {
        // push command to front of parameters array
        array_unshift($params, $command);

        // set input for the command.
        if ($input !== null) {
            $this->instance->input = $input;
        }

        // toggle tagged output.
        $this->instance->tagged = (bool) $tagged;

        // establish connection to perforce server.
        if (!$this->isConnected()) {
            $this->connect();
        }

        // run command.
        $data = call_user_func_array(array($this->instance, "run"), $params);

        // collect data in result object and ensure output is in array form.
        $result = new CommandResult($command, $data, $tagged);
        $result->setErrors($this->instance->errors);
        $result->setWarnings($this->instance->warnings);

        return $result;
    }

    /**
     * Prepare input for passing to the p4 extension.
     * Ensure input is either a string or an array of strings.
     *
     * @param   string|array    $input      the input to prepare for p4.
     * @param   string          $command    the command to prepare input for.
     * @return  string|array    the prepared input.
     */
    protected function prepareInput($input, $command)
    {
        // if input is not an array, cast to string and return.
        if (!is_array($input)) {
            return (string) $input;
        }

        // ensure each element of array is a string.
        $stringify = function (&$input) {
            $input = (string) $input;
        };
        array_walk_recursive($input, $stringify);

        return $input;
    }

    /**
     * Does real work of establishing connection. Called by connect().
     *
     * @throws  Exception\ConnectException    if the connection fails.
     */
    protected function doConnect()
    {
        // temporarily enable exceptions to catch connection failure.
        $this->instance->exception_level = 1;
        try {
            $this->instance->connect();
            $this->instance->exception_level = 0;
        } catch (\P4_Exception $e) {
            $this->instance->exception_level = 0;
            throw new Exception\ConnectException(
                "Connect failed: " . $e->getMessage()
            );
        }
    }
}
# Change User Description Committed
#1 18334 Liz Lam initial add of jambox