Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions classes/Backtrace.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
}

if ( ! class_exists( 'QM_Backtrace' ) ) {
/**
* @deprecated Use QM_StackTrace instead
*/
class QM_Backtrace {

/**
Expand Down
183 changes: 183 additions & 0 deletions classes/Callback.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
<?php declare(strict_types = 1);
/**
* Class that represents a valid callback.
*
* @package query-monitor
*/

final class QM_Callback {
/**
* @var string
*/
public $name;

/**
* @var ?string
*/
public $file;

/**
* @var ?int
*/
public $line;

/**
* @var QM_Component
*/
public $component;

/**
* Create an instance from a callback that may or may not be a valid callable.
*
* @param mixed $callback
* @throws QM_CallbackException
*/
public static function from_callable( $callback ): self {
if ( is_string( $callback ) && ( false !== strpos( $callback, '::' ) ) ) {
// Static class method:
$callback = explode( '::', $callback );
}

if ( is_array( $callback ) ) {
return self::from_method( $callback );
}

if ( $callback instanceof Closure ) {
return self::from_closure( $callback );
}

if ( is_object( $callback ) ) {
return self::from_invokable( $callback );
}

if ( is_string( $callback ) ) {
return self::from_function( $callback );
}

throw ( new QM_CallbackException( 'Invalid callback' ) )->add_data( (string) $callback );
}

/**
* @param array $callback
* @phpstan-param array{
* 0: string|object,
* 1: string,
* } $callback
* @throws QM_CallbackException
*/
protected static function from_method( array $callback ): self {
// Static class method:
$class = $callback[0];
$access = '::';

if ( is_object( $class ) ) {
// Class instance method
$class = get_class( $class );
$access = '->';
}

$name = QM_Util::shorten_fqn( $class . $access . $callback[1] ) . '()';

try {
$ref = new ReflectionMethod( $callback[0], $callback[1] );
} catch ( ReflectionException $e ) {
throw ( new QM_CallbackException( $e->getMessage(), $e->getCode(), $e ) )->add_data( $name );
}

return self::init( $name, $ref );
}

/**
* @param Closure $callback
* @throws QM_CallbackException
*/
protected static function from_closure( Closure $callback ): self {
$name = '{closure}';

try {
$ref = new ReflectionFunction( $callback );
} catch ( ReflectionException $e ) {
throw ( new QM_CallbackException( $e->getMessage(), $e->getCode(), $e ) )->add_data( $name );
}

$filename = $ref->getFileName();

if ( $filename ) {
$file = QM_Util::standard_dir( $filename, '' );
if ( 0 === strpos( $file, '/' ) ) {
$file = basename( $filename );
}
$name = sprintf(
/* translators: A closure is an anonymous PHP function. 1: Line number, 2: File name */
__( 'Closure on line %1$d of %2$s', 'query-monitor' ),
$ref->getStartLine(),
$file
);
}

return self::init( $name, $ref );
}

/**
* @param object $callback
* @throws QM_CallbackException
*/
protected static function from_invokable( object $callback ): self {
$class = get_class( $callback );
$name = QM_Util::shorten_fqn( $class ) . '->__invoke()';

try {
$ref = new ReflectionMethod( $class, '__invoke' );
} catch ( ReflectionException $e ) {
throw ( new QM_CallbackException( $e->getMessage(), $e->getCode(), $e ) )->add_data( $name );
}

return self::init( $name, $ref );
}

/**
* @param string $callback
* @throws QM_CallbackException
*/
protected static function from_function( string $callback ): self {
$name = QM_Util::shorten_fqn( $callback ) . '()';

try {
$ref = new ReflectionFunction( $callback );
} catch ( ReflectionException $e ) {
throw ( new QM_CallbackException( $e->getMessage(), $e->getCode(), $e ) )->add_data( $name );
}

$func = self::init( $name, $ref );

if ( $func->file && '__lambda_func' === $ref->getName() ) {
preg_match( '#(?P<file>.*)\((?P<line>[0-9]+)\)#', $func->file, $matches );

$func->file = $matches['file'];
$func->line = (int) $matches['line'];
}

return $func;
}

protected static function init( string $name, ReflectionFunctionAbstract $ref ): self {
$callback = new self();

$callback->name = $name;
$callback->file = $ref->getFileName() ?: null;
$callback->line = $ref->getStartLine() ?: null;

if ( $callback->file ) {
$callback->component = QM_Util::get_file_component( $callback->file );
} else {
$callback->component = new QM_Component();
$callback->component->type = 'php';
$callback->component->name = 'PHP';
$callback->component->context = '';
}

return $callback;
}

protected function __construct() {}
}
8 changes: 8 additions & 0 deletions classes/CallbackException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php declare(strict_types = 1);
/**
* Class that represents an error during creation of a callback.
*
* @package query-monitor
*/

final class QM_CallbackException extends QM_Exception {}
15 changes: 8 additions & 7 deletions classes/Collector.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
*/

if ( ! class_exists( 'QM_Collector' ) ) {
/**
* @phpstan-import-type CapCheck from QM_Collector_Caps
*/
abstract class QM_Collector {

/**
Expand Down Expand Up @@ -215,14 +218,14 @@ final public function process_concerns() {

foreach ( $concerned_actions as $action ) {
if ( has_action( $action ) ) {
$this->concerned_actions[ $action ] = QM_Hook::process( $action, $wp_filter, true, false );
$this->concerned_actions[ $action ] = QM_Hook::process( $action, $wp_filter[ $action ], true, false );
}
$tracked[] = $action;
}

foreach ( $concerned_filters as $filter ) {
if ( has_filter( $filter ) ) {
$this->concerned_filters[ $filter ] = QM_Hook::process( $filter, $wp_filter, true, false );
$this->concerned_filters[ $filter ] = QM_Hook::process( $filter, $wp_filter[ $filter ], true, false );
}
$tracked[] = $filter;
}
Expand All @@ -244,7 +247,7 @@ final public function process_concerns() {
$option
);
if ( has_filter( $filter ) ) {
$this->concerned_filters[ $filter ] = QM_Hook::process( $filter, $wp_filter, true, false );
$this->concerned_filters[ $filter ] = QM_Hook::process( $filter, $wp_filter[ $filter ], true, false );
}
$tracked[] = $filter;
}
Expand Down Expand Up @@ -311,13 +314,11 @@ public static function hide_qm() {

/**
* @param array<string, mixed> $item
* @phpstan-param array{
* component: QM_Component,
* } $item
* @phpstan-param CapCheck $item
* @return bool
*/
public function filter_remove_qm( array $item ) {
return ( 'query-monitor' !== $item['component']->context );
return ( 'query-monitor' !== $item['trace']->get_component()->context );
}

/**
Expand Down
26 changes: 26 additions & 0 deletions classes/Exception.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php declare(strict_types = 1);
/**
* Class that represents a generic error exception.
*
* @package query-monitor
*/

abstract class QM_Exception extends Exception {
/**
* @var mixed
*/
protected $data = '';

final public function to_wp_error(): WP_Error {
return new WP_Error( 'qm-' . $this->getCode(), $this->getMessage(), $this->data );
}

/**
* @param mixed $data
*/
final public function add_data( $data ): self {
$this->data = $data;

return $this;
}
}
33 changes: 14 additions & 19 deletions classes/Hook.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,52 +9,47 @@ class QM_Hook {

/**
* @param string $name
* @param array<string, WP_Hook> $wp_filter
* @param ?WP_Hook $hook
* @param bool $hide_qm
* @param bool $hide_core
* @return array<int, array<string, mixed>>
* @phpstan-return array{
* name: string,
* actions: list<array{
* priority: int,
* callback: array<string, mixed>,
* callback: QM_Callback|WP_Error,
* }>,
* parts: list<string>,
* components: array<string, string>,
* components: list<string>,
* }
*/
public static function process( $name, array $wp_filter, $hide_qm = false, $hide_core = false ) {
public static function process( $name, WP_Hook $hook = null, $hide_qm = false, $hide_core = false ) {

$actions = array();
$components = array();

if ( isset( $wp_filter[ $name ] ) ) {

# http://core.trac.wordpress.org/ticket/17817
$action = $wp_filter[ $name ];

foreach ( $action as $priority => $callbacks ) {
if ( $hook instanceof \WP_Hook ) {
foreach ( $hook as $priority => $callbacks ) {

foreach ( $callbacks as $cb ) {

$callback = QM_Util::populate_callback( $cb );

if ( isset( $callback['component'] ) ) {
try {
$callback = QM_Callback::from_callable( $cb['function'] );
if (
( $hide_qm && 'query-monitor' === $callback['component']->context )
|| ( $hide_core && 'core' === $callback['component']->context )
( $hide_qm && 'query-monitor' === $callback->component->context )
|| ( $hide_core && 'core' === $callback->component->context )
) {
continue;
}

$components[ $callback['component']->name ] = $callback['component']->name;
$components[] = $callback->component->name;
} catch ( QM_CallbackException $e ) {
$callback = $e->to_wp_error();
}

$actions[] = array(
'priority' => $priority,
'callback' => $callback,
);

}
}
}
Expand All @@ -65,7 +60,7 @@ public static function process( $name, array $wp_filter, $hide_qm = false, $hide
'name' => $name,
'actions' => $actions,
'parts' => $parts,
'components' => $components,
'components' => array_unique( $components ),
);

}
Expand Down
Loading