<?php
/**
* P4PHP Perforce limit handler.
*
* This handler allows the user to specify a maximum number of output blocks
* to report. If more are received, they will go unreported and the command
* cancelled.
*
* Additionally, you can specify an 'filter' callback. If set, blocks failing
* the filter will not be reported in the result and won't count against max
* if a limit has been specified.
*
* @copyright 2013 Perforce Software. All rights reserved.
* @license Please see LICENSE.txt in top-level folder of this distribution.
* @version <release>/<patch>
*/
namespace P4\OutputHandler;
class Limit extends \P4_OutputHandlerAbstract
{
// these constants are also defined by the perforce extension
// but we duplicate a copy here for clarity and convenience.
const HANDLER_REPORT = 0;
const HANDLER_HANDLED = 1;
const HANDLER_CANCEL = 2;
const FILTER_INCLUDE = true;
const FILTER_EXCLUDE = false;
const FILTER_SKIP = null;
protected $outputCallback = null;
protected $filterCallback = null;
protected $countAll = false;
protected $max = 0;
protected $count = 0;
protected $total = 0;
protected $cancelled = false;
/**
* The output function will be called for each output block.
* The passed 'callable' will receive two params:
* $data - string or array of data being output
* $type - one of stat, info, text, binary
*
* The callback should return one or more bit flags to control
* reporting/cancelling:
* HANDLER_REPORT = 0;
* HANDLER_HANDLED = 1;
* HANDLER_CANCEL = 2;
*
* The output callback won't see any data blocks that have been
* blocked via the filter callback. It will also stop being called
* once 'max' blocks have been seen if a max is in use.
*
* @param callable|null $callback the output function to use or null
* @return Limit to maintain a fluent interface
* @throws \InvalidArgumentException if callback isn't callable or null
*/
public function setOutputCallback($callback = null)
{
if (!is_callable($callback) && !is_null($callback)) {
throw new \InvalidArgumentException('Output callback must be callable or null.');
}
$this->outputCallback = $callback;
return $this;
}
/**
* Return the current output callback or null if none set.
*
* @return callable|null the current output callback or null
*/
public function getOutputCallback()
{
return $this->outputCallback;
}
/**
* The filter function will be called for each output block and can return
* true to allow the output block into the result or false to screen it out.
* The passed 'callable' will receive two params:
* $data - string or array of data being output
* $type - one of stat, info, text, binary
*
* Filtered results will not count towards 'max' if a limit is set.
*
* @param callable|null $filter the filter to use or null
* @return Limit to maintain a fluent interface
* @throws \InvalidArgumentException if filter isn't callable or null
*/
public function setFilterCallback($filter = null)
{
if (!is_callable($filter) && !is_null($filter)) {
throw new \InvalidArgumentException('Filter must be callable or null.');
}
$this->filterCallback = $filter;
return $this;
}
/**
* Return the current filter callback or null if none set.
*
* @return callable|null the current 'filter' callback or null
*/
public function getFilterCallback()
{
return $this->filterCallback;
}
/**
* Specify a the maximum number of entries to return or leave 0/null for all.
* If a 'filter' callback is in place values failing the filter don't count.
*
* @param int|null $max the maximum number of entries to return (or 0/null for unlimited)
* @return Limit to maintain a fluent interface
*/
public function setMax($max)
{
$this->max = (int) $max;
return $this;
}
/**
* Returns the current max setting.
*
* @return int|null the current 'max' setting (0/null for unlimited)
*/
public function getMax()
{
return $this->max;
}
/**
* Returns the 'total' value.
*
* @return int the current 'total' value
*/
public function getTotalCount()
{
return $this->total;
}
/**
* Specify whether output will be cancelled after maximum entries was reached
*
* @param bool $countAll if true then output will not be cancelled after
* reaching maximum entries
* @return Limit to maintain a fluent interface
*/
public function setCountAll($countAll)
{
$this->countAll = (bool) $countAll;
return $this;
}
/**
* Return the current countAll setting.
*
* @return bool the current countAll setting
*/
public function getCountAll()
{
return $this->countAll;
}
/**
* Called automatically by 'runHandler' to reset per-run tracking
* variables. (i.e. our max progress).
*
* @return Limit to maintain a fluent interface
*/
public function reset()
{
$this->count = 0;
$this->total = 0;
$this->cancelled = false;
return $this;
}
/**
* Simply calls through to handle output for stat blocks.
*
* @param mixed $data the data being output
* @return int handler bit flags controlling reporting/cancelling
*/
public function outputStat($data)
{
return $this->handleOutput($data, 'stat');
}
/**
* Simply calls through to handle output for info blocks.
*
* @param mixed $data the data being output
* @return int handler bit flags controlling reporting/cancelling
*/
public function outputInfo($data)
{
return $this->handleOutput($data, 'info');
}
/**
* Simply calls through to handle output for text blocks.
*
* @param string $data the data being output
* @return int handler bit flags controlling reporting/cancelling
*/
public function outputText($data)
{
return $this->handleOutput($data, 'text');
}
/**
* Simply calls through to handle output for binary blocks.
*
* @param string $data the data being output
* @return int handler bit flags controlling reporting/cancelling
*/
public function outputBinary($data)
{
return $this->handleOutput($data, 'binary');
}
/**
* Check if the last command was cancelled.
*
* @return bool true if the last command was cancelled, false otherwise
*/
public function wasCancelled()
{
return $this->cancelled;
}
/**
* We tell the output function not to report if we have a filter and
* the data does not pass. These skipped entries won't count against max.
*
* If a output callback is in use, it will be invoked for every
* block that passes the filter callback (up to the max limit).
*
* Lastly, if we received more than 'max' entries (assuming a limit
* has been set) we will skip reporting additional data and cancel.
*
* @param mixed $data the data being output
* @param string $type one of stat, info, text or binary
* @return int handler bit flags controlling reporting/cancelling
*/
protected function handleOutput($data, $type)
{
$isMaxReached = $this->max && $this->count >= $this->max;
// if we get more entries back than needed and we are not counting all,
// skip them and cancel
if ($isMaxReached && !$this->getCountAll()) {
$this->cancelled = true;
return self::HANDLER_HANDLED | self::HANDLER_CANCEL;
}
// filter entries prior to counting them or passing to output callback
$filterCallback = $this->getFilterCallback();
$filterResult = $filterCallback ? $filterCallback($data, $type) : static::FILTER_INCLUDE;
// update 'count' and 'total' counters based on the filter result
$this->count += $filterResult === static::FILTER_INCLUDE ? 1 : 0;
$this->total += $filterResult !== static::FILTER_EXCLUDE ? 1 : 0;
// return handled if we don't hit good entry or are over maximum
if ($filterResult !== static::FILTER_INCLUDE || $isMaxReached) {
return self::HANDLER_HANDLED;
}
// if we have an output callback return its result otherwise return REPORT
$outputCallback = $this->getOutputCallback();
$outputResult = $outputCallback ? $outputCallback($data, $type) : self::HANDLER_REPORT;
$this->cancelled = $this->cancelled || ($outputResult & self::HANDLER_CANCEL);
return $outputResult;
}
}