Perforce Chronicle 2012.2/486814
API Documentation

P4_File_Filter Class Reference

Constructs fstat filter expressions specifically for filtering files via fetchAll(). More...

Inheritance diagram for P4_File_Filter:
P4Cms_Record_Filter

List of all members.

Public Member Functions

 __construct ($stringFilter=null)
 The constructor accepts an optional existing string filter as the intial condition.
 __toString ()
 Automatically generate filter expression when cast to a string.
 add ($field, $value, $comparison=self::COMPARE_EQUAL, $connective=self::CONNECTIVE_AND, $caseInsensitive=null)
 Add a fstat field condition to the filter.
 addSubFilter ($filter, $connective=self::CONNECTIVE_AND)
 Add a group of conditions to this filter.
 escapeForEquals ($value)
 Escape the given value for use in a filter expression in order to return literal matches.
 escapeForRegex ($value)
 Escape the given value for use in a filter expression in order to return regex matches.
 getExpression ()
 Generate fstat filter expression.

Static Public Member Functions

static create ($stringFilter=null)
 Creates and returns a new Filter class.
static getComparisonOperators ()
 Get a list of all known comparison operators.
static getConnectiveOperators ()
 Get a list of all known connective operators.
static getInvertedOperator ($operator)
 Invert the given operator.
static isNegatedOperator ($operator)
 Check if the given operator is negated.

Public Attributes

const COMPARE_CONTAINS = '~'
const COMPARE_EQUAL = '='
const COMPARE_GT = '>'
const COMPARE_GTE = '>='
const COMPARE_LT = '<'
const COMPARE_LTE = '<='
const COMPARE_NOT_CONTAINS = '!~'
const COMPARE_NOT_EQUAL = '!='
const COMPARE_NOT_REGEX = '!~='
const COMPARE_REGEX = '~='
const CONNECTIVE_AND = '&'
const CONNECTIVE_AND_NOT = '&^'
const CONNECTIVE_OR = '|'
const CONNECTIVE_OR_NOT = '|^'
const GROUP_CLOSE = ')'
const GROUP_OPEN = '('
const LOGICAL_NOT = '^'

Protected Member Functions

 _add ($field, $value, $comparison=self::COMPARE_EQUAL, $connective=self::CONNECTIVE_AND, $caseInsensitive=null)
 Add a fstat field condition to the filter.

Protected Attributes

 $_conditions = array()

Detailed Description

Constructs fstat filter expressions specifically for filtering files via fetchAll().

Copyright:
2011-2012 Perforce Software. All rights reserved
License:
Please see LICENSE.txt in top-level folder of this distribution.
Version:
2012.2/486814

Constructor & Destructor Documentation

P4_File_Filter::__construct ( stringFilter = null)

The constructor accepts an optional existing string filter as the intial condition.

Parameters:
string$stringFilterAn existing string filter.

Reimplemented in P4Cms_Record_Filter.

    {
        if (is_string($stringFilter)) {
            $this->_conditions[] = array(
                'field'             => $stringFilter,
                'value'             => '',
                'comparison'        => '',
                'connective'        => static::CONNECTIVE_AND,
                'caseInsensitive'   => null
            );
        }
    }

Member Function Documentation

P4_File_Filter::__toString ( )

Automatically generate filter expression when cast to a string.

Returns:
string The generated fstat filter expression.
    {
        return $this->getExpression();
    }
P4_File_Filter::_add ( field,
value,
comparison = self::COMPARE_EQUAL,
connective = self::CONNECTIVE_AND,
caseInsensitive = null 
) [protected]

Add a fstat field condition to the filter.

Implemented as a protected as extenders will likely shift meaning of 'add' function but we need a reliable, locatable, low level copy.

Parameters:
string$fieldFstat field to filter on
null | string | array$valueValue we are comparing to as string, null or array of strings. If an array is given, condition will pass if any of the values satisfy the comparison.
string$comparisonoptional - comparison operator to use, defaults to Equal
string$connectiveoptional - logical connective operator
null | boolean$caseInsensitiveoptional - case-insensitive matching preference, default to null.
Returns:
P4_File_Filter To maintain a fluent interface.
    {
        if (!is_string($field) || !strlen($field)) {
            throw new InvalidArgumentException(
                "Cannot add condition. Field must be a non-empty string."
            );
        }

        if ((is_array($value) && !count($value))) {
            throw new InvalidArgumentException(
                "Cannot add condition. Value must be null, a string or an array of strings."
            );
        }

        if (!is_array($value) && !is_string($value) && $value !== null) {
            throw new InvalidArgumentException(
                "Cannot add condition. Value must be null, a string or an array of strings."
            );
        }

        if (!in_array($comparison, static::getComparisonOperators())) {
            throw new InvalidArgumentException(
                "Cannot add condition. Invalid comparison operator specified."
            );
        }

        if (!isset($connective)) {
            $connective = static::CONNECTIVE_AND;
        }
        if (!in_array($connective, static::getConnectiveOperators())) {
            throw new InvalidArgumentException(
                "Cannot add condition. Invalid connective specified."
            );
        }

        // if value is an array, create a sub filter and compare field
        // against each value using connective or's
        if (is_array($value)) {
            $values = $value;

            // one last check the values array has valid entries
            if (count(array_filter($values, 'is_string')) != count($values)) {
                throw new InvalidArgumentException(
                    "Cannot add condition. Value array must contain only strings."
                );
            }

            // if the comparison is negated, we assume the caller wants 
            // to match things NOT IN this set - therefore we invert the 
            // comparison and move the negation to the connective.
            if (static::isNegatedOperator($comparison)) {
                $comparison = static::getInvertedOperator($comparison);
                $connective = static::getInvertedOperator($connective);
            }
            
            // create and glue on sub-filter
            $subFilter = new static;
            foreach ($values as $value) {
                $subFilter->_add($field, $value, $comparison, static::CONNECTIVE_OR);
            }
            $this->addSubFilter($subFilter, $connective);

            return $this;
        }

        $this->_conditions[] = array(
            'field'             => $field,
            'value'             => $value,
            'comparison'        => $comparison,
            'connective'        => $connective,
            'caseInsensitive'   => $caseInsensitive
        );

        return $this;
    }
P4_File_Filter::add ( field,
value,
comparison = self::COMPARE_EQUAL,
connective = self::CONNECTIVE_AND,
caseInsensitive = null 
)

Add a fstat field condition to the filter.

Parameters:
string$fieldFstat field to filter on
null | string | array$valueValue we are comparing to as string, null or array of strings. If an array is given, condition will pass if any of the values satisfy the comparison.
string$comparisonoptional - comparison operator to use, defaults to Equal
string$connectiveoptional - logical connective operator
null | boolean$caseInsensitiveoptional - case-insensitive matching preference, default to null.
Returns:
P4_File_Filter To maintain a fluent interface.

Reimplemented in P4Cms_Record_Filter.

    {
        return $this->_add($field, $value, $comparison, $connective, $caseInsensitive);
    }
P4_File_Filter::addSubFilter ( filter,
connective = self::CONNECTIVE_AND 
)

Add a group of conditions to this filter.

Parameters:
P4_File_Filter$filterThe sub-filter to add
string$connectiveoptional - logical connective operator
Returns:
P4_File_Filter To maintain a fluent interface.
    {
        if (!$filter instanceof P4Cms_Record_Filter) {
            throw new InvalidArgumentException(
                "Cannot add sub-filter. Invalid type passed."
            );
        }

        if (!in_array($connective, static::getConnectiveOperators())) {
            throw new InvalidArgumentException(
                "Cannot add sub-filter. Invalid connective specified."
            );
        }

        $this->_conditions[] = array(
            'filter'            => $filter,
            'connective'        => $connective,
            'caseInsensitive'   => null
        );

        return $this;
    }
static P4_File_Filter::create ( stringFilter = null) [static]

Creates and returns a new Filter class.

Useful for nesting conditions or working around PHP's lack of new chaining.

Parameters:
string$stringFilterAn existing string filter.
Returns:
P4_File_Filter
    {
        return new static($stringFilter);
    }
P4_File_Filter::escapeForEquals ( value)

Escape the given value for use in a filter expression in order to return literal matches.

Parameters:
string$valueThe value to escape for use in an equals filter.
Returns:
string The escaped value.
    {
        // Escape anything other than alpha/numeric in value string.
        // As we're using regex-based filtering throughout, we need to use multiple passes
        // of escaping for certain characters when using COMPARE_LIKE/COMPARE_NOT_LIKE
        $regexes = array(
            '/([^a-zA-Z0-9])/',        // escaping non-alphanumeric chars is fairly obvious
            '/([\n\r$^*()\\[\\]|?])/', // double-escaping common regex characters is required
            '/([\n\r$^()\\[\\]|])/'    // triple-escaping these characters is required
        );

        return preg_replace($regexes, '\\\$1', $value);
    }
P4_File_Filter::escapeForRegex ( value)

Escape the given value for use in a filter expression in order to return regex matches.

Parameters:
string$valueThe value to escape for use in a regex filter.
Returns:
string The escaped value.
    {
        return preg_replace('/([^a-zA-Z0-9*\[\]?.+])/', '\\\$1', $value);
    }
static P4_File_Filter::getComparisonOperators ( ) [static]

Get a list of all known comparison operators.

Returns:
array All known comparison values
    {
        return array(
            static::COMPARE_EQUAL,
            static::COMPARE_NOT_EQUAL,
            static::COMPARE_CONTAINS,
            static::COMPARE_NOT_CONTAINS,
            static::COMPARE_REGEX,
            static::COMPARE_NOT_REGEX,
            static::COMPARE_GT,
            static::COMPARE_LT,
            static::COMPARE_GTE,
            static::COMPARE_LTE
        );
    }
static P4_File_Filter::getConnectiveOperators ( ) [static]

Get a list of all known connective operators.

Returns:
array All known connective values
    {
        return array(
            static::CONNECTIVE_AND,
            static::CONNECTIVE_AND_NOT,
            static::CONNECTIVE_OR,
            static::CONNECTIVE_OR_NOT
        );
    }
P4_File_Filter::getExpression ( )

Generate fstat filter expression.

Returns:
string The generated fstat filter expression.
    {
        $expression = '';

        foreach ($this->_conditions as $condition) {
            // turn array key/value pairs into named variables
            extract($condition);

            // skip empty sub-filters.
            $isSubFilter   = array_key_exists('filter', $condition);
            $subExpression = $isSubFilter ? $filter->getExpression() : null;
            if ($isSubFilter && empty($subExpression)) {
                continue;
            }

            // add in the connective if this isn't the first condition
            // if it is the first condition, and the connective is negated
            // start the expression with a logical not.
            if ($expression !== '') {
                $expression .= ' ' . $connective . ' ';
            } else if (static::isNegatedOperator($connective)) {
                $expression = static::LOGICAL_NOT;
            }

            // if this condition is a sub-filter, wrap sub-expression in group operators
            if ($isSubFilter) {
                $expression .= static::GROUP_OPEN . $subExpression . static::GROUP_CLOSE;
                continue;
            }

            // escape the provided value so it is safe to use in a filter clause.
            $value = ($comparison === static::COMPARE_REGEX || $comparison === static::COMPARE_NOT_REGEX)
                ? $this->escapeForRegex($condition['value'])
                : $this->escapeForEquals($condition['value']);

            // produce a null value so we can match empty/null attributes,
            // but only when we have a comparison.
            if ($value === '' and $comparison !== '') {
                $comparison = static::COMPARE_REGEX;
                $value = $this->escapeForRegex('^$');
            }

            // Perforce doesn't support '!=' style operators, so we must
            // switch the operator over to positive and prepend a logical not.
            if ($comparison === static::COMPARE_NOT_EQUAL
                || $comparison === static::COMPARE_NOT_REGEX
                || $comparison === static::COMPARE_NOT_CONTAINS
            ) {
                $expression .= static::LOGICAL_NOT;
                if ($comparison === static::COMPARE_NOT_EQUAL) {
                    $comparison = static::COMPARE_EQUAL;
                } else if ($comparison === static::COMPARE_NOT_REGEX) {
                    $comparison = static::COMPARE_REGEX;
                } else if ($comparison === static::COMPARE_NOT_CONTAINS) {
                    $comparison = static::COMPARE_CONTAINS;
                }
            }

            // convert equals and contains operators to regex because it is
            // more accurate (not all characters can be escaped for the equals
            // operator and it doesn't support case insensitive comparisons).
            // to convert equals, we must bind to the start/end of the value
            // to ensure a literal match.
            if ($comparison === static::COMPARE_EQUAL) {
                $comparison = static::COMPARE_REGEX;
                $value      = $this->escapeForRegex('^')
                            . $value
                            . $this->escapeForRegex('$');
            } else if ($comparison === static::COMPARE_CONTAINS) {
                $comparison = static::COMPARE_REGEX;
            }

            // when we are matching and ignoring case, we need to compose a
            // suitable regex. since we may have character classes in the
            // provided regex, we track bracketing and try to behave sensibly
            // while adding [Aa] atoms to the regex where appropriate.
            if ($caseInsensitive && $comparison === static::COMPARE_REGEX) {
                $newValue     = '';
                $bracketLevel = 0;
                $escape       = false;
                foreach (str_split($value) as $char) {
                    if ($char === '[' and !$escape) {
                        $bracketLevel++;
                    }
                    if ($char === ']' and !$escape) {
                        if ($bracketLevel-- < 0) {
                            $bracketLevel = 0;
                        }
                    }
                    if (preg_match('/[a-zA-Z]/', $char)) {
                        $startBracket = $bracketLevel > 0 ? '' : '[';
                        $endBracket = $bracketLevel > 0 ? '' : ']';
                        $char = $startBracket . strtoupper($char) . strtolower($char) . $endBracket;
                    }

                    // check for escape characters, set escape state accordingly.
                    $escape = $char === '\\'
                        ? ($escape ? false : true)
                        : false;
                    $newValue .= $char;
                }
                $value = $newValue;
            }

            // glue on the field/comparison/value to our running expression
            $expression .= $field . $comparison . $value;
        }

        return $expression;
    }
static P4_File_Filter::getInvertedOperator ( operator) [static]

Invert the given operator.

Parameters:
string$operatora connective or comparison operator to invert.
Exceptions:
InvalidArgumentExceptionif the given operator cannot be inverted.
    {
        switch ($operator) {
            case static::COMPARE_EQUAL:
                return static::COMPARE_NOT_EQUAL;
            case static::COMPARE_NOT_EQUAL:
                return static::COMPARE_EQUAL;
            case static::COMPARE_CONTAINS:
                return static::COMPARE_NOT_CONTAINS;
            case static::COMPARE_NOT_CONTAINS:
                return static::COMPARE_CONTAINS;
            case static::COMPARE_REGEX:
                return static::COMPARE_NOT_REGEX;
            case static::COMPARE_GT:
                return static::COMPARE_LT;
            case static::COMPARE_LT:
                return static::COMPARE_GT;
            case static::COMPARE_GTE:
                return static::COMPARE_LTE;
            case static::COMPARE_LTE:
                return static::COMPARE_GTE;
            case static::CONNECTIVE_AND:
                return static::CONNECTIVE_AND_NOT;
            case static::CONNECTIVE_AND_NOT:
                return static::CONNECTIVE_AND;
            case static::CONNECTIVE_OR:
                return static::CONNECTIVE_OR_NOT;
            case static::CONNECTIVE_OR_NOT:
                return static::CONNECTIVE_OR;
            default:
                throw new InvalidArgumentException(
                    "Cannot invert operator. Invalid operator given."
                );
        }
    }
static P4_File_Filter::isNegatedOperator ( operator) [static]

Check if the given operator is negated.

Parameters:
string$operatora connective or comparison operator.
Exceptions:
InvalidArgumentExceptionif the given operator is invalid.
    {
        $operators = array_merge(
            static::getComparisonOperators(), 
            static::getConnectiveOperators()
        );
        if (!in_array($operator, $operators)) {
            throw new InvalidArgumentException(
                "Cannot determine if operator is negated. Invalid operator specified."
            );
        }

        return in_array(
            $operator, 
            array(
                static::COMPARE_NOT_EQUAL,
                static::COMPARE_NOT_CONTAINS,
                static::COMPARE_NOT_REGEX,
                static::CONNECTIVE_AND_NOT, 
                static::CONNECTIVE_OR_NOT
            )
        );
    }

Member Data Documentation

P4_File_Filter::$_conditions = array() [protected]

The documentation for this class was generated from the following file: