src/EventCompressor.php
<?php
/**
* Created by Vitaly Iegorov <egorov@samsonos.com>
* on 19.09.2014 at 17:26
*/
namespace samsonphp\compressor;
/**
* Compressor form SamsonPHP Event system
* @author Vitaly Egorov <egorov@samsonos.com>
* @copyright 2014 SamsonOS
*/
class EventCompressor
{
/** @var array Collection of event subscriptions and handlers */
public $subscriptions = array();
/** @var array Collection of event fires */
public $fires = array();
protected function & parseSubscription(array & $matches)
{
// Resulting collection of arrays
$events = array();
// Iterate all matches based on event identifiers
for ($i=0,$l = sizeof($matches['id']); $i < $l; $i++) {
// Pointer to events collection
$event = & $events[trim($matches['id'][$i])];
// Create collection for this event identifier
$event = isset($event) ? $event : array();
// Get handler code
$handler = rtrim(trim($matches['handler'][$i]), ')');
// Add ')' if this is an array callback
$handler = stripos($handler, 'array') !== false ? $handler.')' : $handler;
// Create subscription metadata
$metadata = array(
'source' => $matches[0][$i]
);
// If this is object method callback - parse it
$args = array();
if (preg_match('/\s*array\s*\((?<object>[^,]+)\s*,\s*(\'|\")(?<method>[^\'\"]+)/ui', $handler, $args)) {
// If this is static
$metadata['object'] = $args['object'];
$metadata['method'] = $args['method'];
} else { //global function
$metadata['method'] = str_replace(array('"',"'"), '', $handler);
}
// Add event callback
$event[] = $metadata;
}
return $events;
}
/**
* Find and gather all static event subscription calls
* @param string $code PHP code for searching
*
* @return array Collection event subscription collection
*/
public function findAllStaticSubscriptions($code)
{
// Found collection
$matches = array();
// Matching pattern
$pattern = '/(\\\\samsonphp\\\\event\\\\|samson_core_|\\\samson\\\\core\\\\)*Event::subscribe\s*\(\s*(\'|\")(?<id>[^\'\"]+)(\'|\")\s*,\s*(?<handler>[^;-]+)/ui';
// Perform text search
if (preg_match_all($pattern, $code, $matches)) {
// Additional handling
}
// Call generic subscription parser
return $this->parseSubscription($matches);
}
/**
* Find all event fire calls in code
*
* @param $code
*
* @return array
*/
public function findAllFires($code)
{
// Resulting collection of arrays
$events = array();
// Found collection
$matches = array();
// Matching pattern
$pattern = '/(\\\\samsonphp\\\\event\\\\|samson_core_|\\\samson\\\core\\\)?Event::(fire|signal)\s*\(\s*(\'|\")(?<id>[^\'\"]+)(\'|\")\s*(,\s*(?<params>[^;]+)|\s*\))?/ui';
// TODO: Move to token_get_all();
// Perform text search
if (preg_match_all($pattern, $code, $matches)) {
// Iterate all matches based on event identifiers
for ($i=0,$l = sizeof($matches['id']); $i < $l; $i++) {
// Get handler code
$params = trim($matches['params'][$i]);
// If this is signal fire - remove last 'true' parameter
$match = array();
if (preg_match('/\),\s*true\s*\)/', $params, $match)) {
$params = str_replace($match[0], '', $params);
}
// Remove spaces and new lines
$params = preg_replace('/[ \t\n\r]+/', '', $params);
// Parse all fire parameters
$args = array();
if (preg_match('/\s*array\s*\((?<parameters>[^;]+)/ui', $params, $args)) {
// Remove reference symbol as we do not need it
$params = array();
foreach (explode(',', $args['parameters']) as $parameter) {
$params[] = str_replace(array('))', '&'), '', $parameter);
}
}
// Add event callback
$events[trim($matches['id'][$i])] = array(
'params' => $params,
'source' => $matches[0][$i]
);
}
}
return $events;
}
/**
* Find and gather all dynamic event subscription calls
* @param string $code PHP code for searching
*
* @return array Collection event subscription collection
*/
public function findAllDynamicSubscriptions($code)
{
// Found collection
$matches = array();
// Matching pattern
$pattern = '/\s*->\s*subscribe\s*\(\s*(\'|\")(?<id>[^\'\"]+)(\'|\")\s*,\s*(?<handler>[^;-]+)/ui';
// Perform text search
if (preg_match_all($pattern, $code, $matches)) {
// Additional handling
}
// Call generic subscription parser
return $this->parseSubscription($matches);
}
/**
* Analyze code and gather event system calls
* @param string $input PHP code for analyzing
*/
public function collect($input)
{
// Gather all subscriptions
$this->subscriptions = array_merge_recursive(
$this->subscriptions,
$this->findAllDynamicSubscriptions($input),
$this->findAllStaticSubscriptions($input)
);
// Gather events fires
$this->fires = array_merge_recursive(
$this->fires,
$this->findAllFires($input)
);
}
/**
* Remove all event subscription calls
* @param array $subscriptions collection of subscription groups
* @param string $code Code for removing subscriptions
* @return string Modified code with removed subscriptions(if were present)
*/
public function removeSubscriptionCalls($subscriptions, $code)
{
// Iterate all subscription groups
foreach ($subscriptions as $eventID => $subscriptionGroup) {
// Iterate all subscriptions in group
foreach ($subscriptionGroup as $subscription) {
// Remove all event subscription in code
$code = str_replace($subscription['source'], '', $code);
$this->log('Removing event [##] subscription [##]', $eventID, $subscription['source']);
}
}
return $code;
}
public function transform($input, & $output = '')
{
// Get all defined handlers
$handlers = \samsonphp\event\Event::listeners();
// Iterate all event fire calls
foreach ($this->fires as $id => $data) {
// Collection of actual event handler call for replacement
$code = array();
// Set pointer to event subscriptions collection
if (isset($this->subscriptions[$id])) {
// Iterate event subscriptions
foreach ($this->subscriptions[$id] as &$event) {
$this->log('Analyzing event subscription[##]', $id);
// If subscriber callback is object method
if (isset($event['object'])) {
$eventHandlers = & $handlers[$id];
if (isset($eventHandlers)) {
// Iterate all handlers
foreach ($eventHandlers as $handler) {
//trace($handler);
$call = '';
// Get pointer to object
if (is_scalar($handler[0][0])) {
$object = $handler[0][0];
} else {
$object = & $handler[0][0];
}
// TODO: Not existing dynamic handlers what was excluded from compressed code
if(is_object($object) && $object instanceof \samsonframework\core\ViewInterface && $object instanceof \samsonframework\core\CompressInterface) {
// Build object method call
$call = 'm("' . $object->id() . '")->' . $event['method'] . '(';
$this->log(' - Replacing event fire[##] with object function call [##]', $id, $call);
} elseif (strpos($event['object'], '(') !== false) { // Function
// Build object method call
$call = $event['object'].'->' . $event['method'] . '(';
} elseif (is_string($object) && class_exists($object, false)) { // Static class
//trace($event['object'].'-'.$object);
// Build object method call
$call = $event['object'].'::' . $event['method'] . '(';
}
// TODO: Define what to do with other classes, only functions supported
// If we have found correct object
if (isset($call{0})) {
// Event fire passes parameters
if (is_array($data['params'])) {
$call .= implode(', ', $data['params']);
}
// Gather object calls
$code[] = $call .');';
} else {
$this->log(' - Cannot replace event fire[##] with [##] - [##]', $id, $event['object'], $event['method']);
}
}
}
} else { // Global function
if (strpos($event['method'], '$') === false) {
$call = $event['method'] . '(' . implode(', ', $data['params']) . ');';
$code[] = $call;
$this->log(' - Replacing event fire[##] with function call [##]', $id, $call);
} else {
$this->log('Cannot replace event fire method with [##] - variables not supported', $event['method']);
}
}
}
// Remove duplicates
$code = array_unique($code);
// Replace Event::fire call with actual handlers
$input = str_replace($data['source'], implode("\n", $code), $input);
// Logging changes in code
foreach ($code as $replace) {
$this->log('Replacing [##] with [##]', $data['source'], $replace);
}
} else { // There is no subscriptions to this event fire
// Remove Event::fire call without subscriptions
$input = str_replace($data['source'], '', $input);
$this->log('Removing event firing [##] as it has no subscriptions', $data['source']);
}
}
// Remove all subscriptions from code
$input = $this->removeSubscriptionCalls($this->subscriptions, $input);
// Copy output
$output = $input;
return true;
}
/** Generic log function for further modification */
protected function log($message)
{
// Get passed vars
$vars = func_get_args();
// Remove first message var
array_shift($vars);
// Render debug message
return trace(debug_parse_markers($message, $vars));
}
}