|
Perforce Chronicle 2012.2/486814
API Documentation
|
Constructs fstat filter expressions specifically for filtering files via fetchAll(). More...
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() | |
Constructs fstat filter expressions specifically for filtering files via fetchAll().
| P4_File_Filter::__construct | ( | $ | stringFilter = null | ) |
The constructor accepts an optional existing string filter as the intial condition.
| string | $stringFilter | An 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
);
}
}
| P4_File_Filter::__toString | ( | ) |
Automatically generate filter expression when cast to a string.
{
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.
| string | $field | Fstat field to filter on |
| null | string | array | $value | Value 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 | $comparison | optional - comparison operator to use, defaults to Equal |
| string | $connective | optional - logical connective operator |
| null | boolean | $caseInsensitive | optional - case-insensitive matching preference, default to null. |
{
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.
| string | $field | Fstat field to filter on |
| null | string | array | $value | Value 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 | $comparison | optional - comparison operator to use, defaults to Equal |
| string | $connective | optional - logical connective operator |
| null | boolean | $caseInsensitive | optional - case-insensitive matching preference, default to null. |
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.
| P4_File_Filter | $filter | The sub-filter to add |
| string | $connective | optional - logical connective operator |
{
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.
| string | $stringFilter | An existing string 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.
| string | $value | The value to escape for use in an equals filter. |
{
// 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.
| string | $value | The value to escape for use in a regex filter. |
{
return preg_replace('/([^a-zA-Z0-9*\[\]?.+])/', '\\\$1', $value);
}
| static P4_File_Filter::getComparisonOperators | ( | ) | [static] |
Get a list of all known comparison operators.
{
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.
{
return array(
static::CONNECTIVE_AND,
static::CONNECTIVE_AND_NOT,
static::CONNECTIVE_OR,
static::CONNECTIVE_OR_NOT
);
}
| P4_File_Filter::getExpression | ( | ) |
Generate 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.
| string | $operator | a connective or comparison operator to invert. |
| InvalidArgumentException | if 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.
| string | $operator | a connective or comparison operator. |
| InvalidArgumentException | if 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
)
);
}
P4_File_Filter::$_conditions = array() [protected] |
| const P4_File_Filter::COMPARE_CONTAINS = '~' |
| const P4_File_Filter::COMPARE_EQUAL = '=' |
| const P4_File_Filter::COMPARE_GT = '>' |
| const P4_File_Filter::COMPARE_GTE = '>=' |
| const P4_File_Filter::COMPARE_LT = '<' |
| const P4_File_Filter::COMPARE_LTE = '<=' |
| const P4_File_Filter::COMPARE_NOT_CONTAINS = '!~' |
| const P4_File_Filter::COMPARE_NOT_EQUAL = '!=' |
| const P4_File_Filter::COMPARE_NOT_REGEX = '!~=' |
| const P4_File_Filter::COMPARE_REGEX = '~=' |
| const P4_File_Filter::CONNECTIVE_AND = '&' |
| const P4_File_Filter::CONNECTIVE_AND_NOT = '&^' |
| const P4_File_Filter::CONNECTIVE_OR = '|' |
| const P4_File_Filter::CONNECTIVE_OR_NOT = '|^' |
| const P4_File_Filter::GROUP_CLOSE = ')' |
| const P4_File_Filter::GROUP_OPEN = '(' |
| const P4_File_Filter::LOGICAL_NOT = '^' |