mirror of
https://github.com/werkraum-media/fdebug.git
synced 2024-12-23 15:16:09 +01:00
First basic version
This commit is contained in:
parent
81d4a787f8
commit
def53b593a
8 changed files with 851 additions and 2 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
/.Build/
|
||||
/composer.lock
|
||||
/vendor/
|
675
Classes/Utility/DebuggerUtility.php
Normal file
675
Classes/Utility/DebuggerUtility.php
Normal file
|
@ -0,0 +1,675 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WerkraumMedia\FDebug\Utility;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2021 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*/
|
||||
|
||||
use ReflectionMethod;
|
||||
use TYPO3\CMS\Core\SingletonInterface;
|
||||
use TYPO3\CMS\Core\Utility\StringUtility;
|
||||
use TYPO3\CMS\Extbase\DomainObject\AbstractDomainObject;
|
||||
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
|
||||
use TYPO3\CMS\Extbase\DomainObject\AbstractValueObject;
|
||||
use TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface;
|
||||
use TYPO3\CMS\Extbase\Object\ObjectManager;
|
||||
use TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy;
|
||||
use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper;
|
||||
use TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager;
|
||||
use TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory;
|
||||
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
|
||||
use TYPO3\CMS\Extbase\Reflection\ReflectionService;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
|
||||
/**
|
||||
* Copies from TYPO3 core, and modified to our needs.
|
||||
*
|
||||
* This class is a backport of the corresponding class of TYPO3 Flow.
|
||||
* All credits go to the TYPO3 Flow team.
|
||||
*
|
||||
* A debugging utility class
|
||||
*/
|
||||
class DebuggerUtility
|
||||
{
|
||||
const PLAINTEXT_INDENT = ' ';
|
||||
const HTML_INDENT = ' ';
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage
|
||||
*/
|
||||
protected static $renderedObjects;
|
||||
|
||||
/**
|
||||
* Hardcoded list of Extbase class names (regex) which should not be displayed during debugging
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $blacklistedClassNames = [
|
||||
'PHPUnit_Framework_MockObject_InvocationMocker',
|
||||
ReflectionService::class,
|
||||
ObjectManager::class,
|
||||
DataMapper::class,
|
||||
PersistenceManager::class,
|
||||
QueryObjectModelFactory::class,
|
||||
ContentObjectRenderer::class
|
||||
];
|
||||
|
||||
/**
|
||||
* Hardcoded list of property names (regex) which should not be displayed during debugging
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $blacklistedPropertyNames = ['warning'];
|
||||
|
||||
/**
|
||||
* Is set to TRUE once the CSS file is included in the current page to prevent double inclusions of the CSS file.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected static $stylesheetEchoed = false;
|
||||
|
||||
/**
|
||||
* Defines the max recursion depth of the dump, set to 8 due to common memory limits
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected static $maxDepth = 8;
|
||||
|
||||
/**
|
||||
* Clear the state of the debugger
|
||||
*/
|
||||
protected static function clearState(): void
|
||||
{
|
||||
self::$renderedObjects = new ObjectStorage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a dump of the given value
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param int $level
|
||||
* @param bool $plainText
|
||||
* @param bool $ansiColors
|
||||
* @return string
|
||||
*/
|
||||
protected static function renderDump($value, int $level, bool $plainText, bool $ansiColors): string
|
||||
{
|
||||
$dump = '';
|
||||
if (is_string($value)) {
|
||||
$croppedValue = strlen($value) > 2000 ? substr($value, 0, 2000) . '...' : $value;
|
||||
if ($plainText) {
|
||||
$dump = self::ansiEscapeWrap('"' . implode(PHP_EOL . str_repeat(self::PLAINTEXT_INDENT, $level + 1), str_split($croppedValue, 76)) . '"', '33', $ansiColors) . ' (' . strlen($value) . ' chars)';
|
||||
} else {
|
||||
$lines = str_split($croppedValue, 76);
|
||||
$lines = array_map(static function (string $line): string {
|
||||
return htmlspecialchars($line, ENT_COMPAT);
|
||||
}, $lines);
|
||||
$dump = sprintf('\'<span class="extbase-debug-string">%s</span>\' (%s chars)', implode('<br />' . str_repeat(self::HTML_INDENT, $level + 1), $lines), strlen($value));
|
||||
}
|
||||
} elseif (is_numeric($value)) {
|
||||
$dump = sprintf('%s (%s)', self::ansiEscapeWrap((string)$value, '35', $ansiColors), gettype($value));
|
||||
} elseif (is_bool($value)) {
|
||||
$dump = $value ? self::ansiEscapeWrap('TRUE', '32', $ansiColors) : self::ansiEscapeWrap('FALSE', '32', $ansiColors);
|
||||
} elseif ($value === null || is_resource($value)) {
|
||||
$dump = gettype($value);
|
||||
} elseif (is_array($value)) {
|
||||
$dump = self::renderArray($value, $level + 1, $plainText, $ansiColors);
|
||||
} elseif (is_object($value)) {
|
||||
if ($value instanceof \Closure) {
|
||||
$dump = self::renderClosure($value, $level + 1, $plainText, $ansiColors);
|
||||
} else {
|
||||
$dump = self::renderObject($value, $level + 1, $plainText, $ansiColors);
|
||||
}
|
||||
}
|
||||
return $dump;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a dump of the given array
|
||||
*
|
||||
* @param array $array
|
||||
* @param int $level
|
||||
* @param bool $plainText
|
||||
* @param bool $ansiColors
|
||||
* @return string
|
||||
*/
|
||||
protected static function renderArray(array $array, int $level, bool $plainText = false, bool $ansiColors = false): string
|
||||
{
|
||||
$content = '';
|
||||
$count = count($array);
|
||||
|
||||
if ($plainText) {
|
||||
$header = self::ansiEscapeWrap('array', '36', $ansiColors);
|
||||
} else {
|
||||
$header = '<span class="extbase-debug-type">array</span>';
|
||||
}
|
||||
$header .= $count > 0 ? '(' . $count . ' item' . ($count > 1 ? 's' : '') . ')' : '(empty)';
|
||||
if ($level >= self::$maxDepth) {
|
||||
if ($plainText) {
|
||||
$header .= ' ' . self::ansiEscapeWrap('max depth', '47;30', $ansiColors);
|
||||
} else {
|
||||
$header .= '<span class="extbase-debug-filtered">max depth</span>';
|
||||
}
|
||||
} else {
|
||||
$content = self::renderCollection($array, $level, $plainText, $ansiColors);
|
||||
if (!$plainText) {
|
||||
$header = ($level > 1 && $count > 0 ? '<input type="checkbox" /><span class="extbase-debug-header" >' : '<span>') . $header . '</span >';
|
||||
}
|
||||
}
|
||||
if ($level > 1 && $count > 0 && !$plainText) {
|
||||
$dump = '<span class="extbase-debugger-tree">' . $header . '<span class="extbase-debug-content">' . $content . '</span></span>';
|
||||
} else {
|
||||
$dump = $header . $content;
|
||||
}
|
||||
return $dump;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a dump of the given object
|
||||
*
|
||||
* @param object $object
|
||||
* @param int $level
|
||||
* @param bool $plainText
|
||||
* @param bool $ansiColors
|
||||
* @return string
|
||||
*/
|
||||
protected static function renderObject(object $object, int $level, bool $plainText = false, bool $ansiColors = false): string
|
||||
{
|
||||
if ($object instanceof LazyLoadingProxy) {
|
||||
$object = $object->_loadRealInstance();
|
||||
if (!is_object($object)) {
|
||||
return gettype($object);
|
||||
}
|
||||
}
|
||||
$header = self::renderHeader($object, $level, $plainText, $ansiColors);
|
||||
if ($level < self::$maxDepth && !self::isBlacklisted($object) && !(self::isAlreadyRendered($object) && $plainText !== true)) {
|
||||
$content = self::renderContent($object, $level, $plainText, $ansiColors);
|
||||
} else {
|
||||
$content = '';
|
||||
}
|
||||
if ($plainText) {
|
||||
return $header . $content;
|
||||
}
|
||||
return '<span class="extbase-debugger-tree">' . $header . '<span class="extbase-debug-content">' . $content . '</span></span>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a dump of the given closure
|
||||
*
|
||||
* @param \Closure $object
|
||||
* @param int $level
|
||||
* @param bool $plainText
|
||||
* @param bool $ansiColors
|
||||
* @return string
|
||||
*/
|
||||
protected static function renderClosure(\Closure $object, int $level, bool $plainText = false, bool $ansiColors = false): string
|
||||
{
|
||||
$header = self::renderHeader($object, $level, $plainText, $ansiColors);
|
||||
if ($level < self::$maxDepth && (!self::isAlreadyRendered($object) || $plainText)) {
|
||||
$content = self::renderContent($object, $level, $plainText, $ansiColors);
|
||||
} else {
|
||||
$content = '';
|
||||
}
|
||||
if ($plainText) {
|
||||
return $header . $content;
|
||||
}
|
||||
return '<span class="extbase-debugger-tree"><input type="checkbox" /><span class="extbase-debug-header">' . $header . '</span><span class="extbase-debug-content">' . $content . '</span></span>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given object or property should be excluded/filtered
|
||||
*
|
||||
* @param object $value A ReflectionProperty or other Object
|
||||
* @return bool TRUE if the given object should be filtered
|
||||
*/
|
||||
protected static function isBlacklisted(object $value): bool
|
||||
{
|
||||
if ($value instanceof \ReflectionProperty) {
|
||||
$result = in_array($value->getName(), self::$blacklistedPropertyNames, true);
|
||||
} else {
|
||||
$result = in_array(get_class($value), self::$blacklistedClassNames, true);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given object was already rendered.
|
||||
*
|
||||
* @param object $object
|
||||
* @return bool TRUE if the given object was already rendered
|
||||
*/
|
||||
protected static function isAlreadyRendered(object $object): bool
|
||||
{
|
||||
return self::$renderedObjects->contains($object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the header of a given object/collection. It is usually the class name along with some flags.
|
||||
*
|
||||
* @param object $object
|
||||
* @param int $level
|
||||
* @param bool $plainText
|
||||
* @param bool $ansiColors
|
||||
* @return string The rendered header with tags
|
||||
*/
|
||||
protected static function renderHeader(object $object, int $level, bool $plainText, bool $ansiColors): string
|
||||
{
|
||||
$dump = '';
|
||||
$persistenceType = null;
|
||||
$className = get_class($object);
|
||||
$classReflection = new \ReflectionClass($className);
|
||||
if ($plainText) {
|
||||
$dump .= self::ansiEscapeWrap($className, '36', $ansiColors);
|
||||
} else {
|
||||
$dump .= '<span class="extbase-debug-type">' . htmlspecialchars($className, ENT_COMPAT) . '</span>';
|
||||
}
|
||||
if (!$object instanceof \Closure) {
|
||||
if ($object instanceof SingletonInterface) {
|
||||
$scope = 'singleton';
|
||||
} else {
|
||||
$scope = 'prototype';
|
||||
}
|
||||
if ($plainText) {
|
||||
$dump .= ' ' . self::ansiEscapeWrap($scope, '44;37', $ansiColors);
|
||||
} else {
|
||||
$dump .= '<span class="extbase-debug-scope">' . $scope . '</span>';
|
||||
}
|
||||
if ($object instanceof AbstractDomainObject) {
|
||||
if ($object->_isDirty()) {
|
||||
$persistenceType = 'modified';
|
||||
} elseif ($object->_isNew()) {
|
||||
$persistenceType = 'transient';
|
||||
} else {
|
||||
$persistenceType = 'persistent';
|
||||
}
|
||||
}
|
||||
if ($object instanceof ObjectStorage && $object->_isDirty()) {
|
||||
$persistenceType = 'modified';
|
||||
}
|
||||
if ($object instanceof AbstractEntity) {
|
||||
$domainObjectType = 'entity';
|
||||
} elseif ($object instanceof AbstractValueObject) {
|
||||
$domainObjectType = 'valueobject';
|
||||
} else {
|
||||
$domainObjectType = 'object';
|
||||
}
|
||||
$persistenceType = $persistenceType === null ? '' : $persistenceType . ' ';
|
||||
if ($plainText) {
|
||||
$dump .= ' ' . self::ansiEscapeWrap($persistenceType . $domainObjectType, '42;30', $ansiColors);
|
||||
} else {
|
||||
$dump .= '<span class="extbase-debug-ptype">' . $persistenceType . $domainObjectType . '</span>';
|
||||
}
|
||||
}
|
||||
if (strpos(implode('|', self::$blacklistedClassNames), get_class($object)) > 0) {
|
||||
if ($plainText) {
|
||||
$dump .= ' ' . self::ansiEscapeWrap('filtered', '47;30', $ansiColors);
|
||||
} else {
|
||||
$dump .= '<span class="extbase-debug-filtered">filtered</span>';
|
||||
}
|
||||
} elseif (self::$renderedObjects->contains($object) && !$plainText) {
|
||||
$dump = '<a href="javascript:;" onclick="document.location.hash=\'#' . spl_object_hash($object) . '\';" class="extbase-debug-seeabove">' . $dump . '<span class="extbase-debug-filtered">see above</span></a>';
|
||||
} elseif ($level >= self::$maxDepth && !$object instanceof \DateTimeInterface) {
|
||||
if ($plainText) {
|
||||
$dump .= ' ' . self::ansiEscapeWrap('max depth', '47;30', $ansiColors);
|
||||
} else {
|
||||
$dump .= '<span class="extbase-debug-filtered">max depth</span>';
|
||||
}
|
||||
} elseif ($level > 1 && !$object instanceof \DateTimeInterface && !$plainText) {
|
||||
if (($object instanceof \Countable && empty($object)) || empty($classReflection->getProperties())) {
|
||||
$dump = '<span>' . $dump . '</span>';
|
||||
} else {
|
||||
$dump = '<input type="checkbox" id="' . spl_object_hash($object) . '" /><span class="extbase-debug-header">' . $dump . '</span>';
|
||||
}
|
||||
}
|
||||
if ($object instanceof \Countable) {
|
||||
$objectCount = count($object);
|
||||
$dump .= $objectCount > 0 ? ' (' . $objectCount . ' items)' : ' (empty)';
|
||||
}
|
||||
if ($object instanceof \DateTimeInterface) {
|
||||
$dump .= ' (' . $object->format(\DateTimeInterface::RFC3339) . ', ' . $object->getTimestamp() . ')';
|
||||
}
|
||||
if ($object instanceof DomainObjectInterface && !$object->_isNew()) {
|
||||
$dump .= ' (uid=' . $object->getUid() . ', pid=' . $object->getPid() . ')';
|
||||
}
|
||||
return $dump;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object $object
|
||||
* @param int $level
|
||||
* @param bool $plainText
|
||||
* @param bool $ansiColors
|
||||
* @return string The rendered body content of the Object(Storage)
|
||||
*/
|
||||
protected static function renderContent(object $object, int $level, bool $plainText, bool $ansiColors): string
|
||||
{
|
||||
$dump = '';
|
||||
if ($object instanceof \Iterator || $object instanceof \ArrayObject) {
|
||||
$dump .= self::renderCollection($object, $level, $plainText, $ansiColors);
|
||||
} else {
|
||||
self::$renderedObjects->attach($object);
|
||||
if (!$plainText) {
|
||||
$dump .= '<a name="' . spl_object_hash($object) . '" id="' . spl_object_hash($object) . '"></a>';
|
||||
}
|
||||
if ($object instanceof \Closure) {
|
||||
$dump .= PHP_EOL . str_repeat(self::PLAINTEXT_INDENT, $level)
|
||||
. ($plainText ? '' : '<span class="extbase-debug-closure">')
|
||||
. self::ansiEscapeWrap('function (', '33', $ansiColors) . ($plainText ? '' : '</span>');
|
||||
|
||||
$reflectionFunction = new \ReflectionFunction($object);
|
||||
$params = [];
|
||||
foreach ($reflectionFunction->getParameters() as $parameter) {
|
||||
$parameterDump = '';
|
||||
if ($parameter->isArray()) {
|
||||
if ($plainText) {
|
||||
$parameterDump .= self::ansiEscapeWrap('array ', '36', $ansiColors);
|
||||
} else {
|
||||
$parameterDump .= '<span class="extbase-debug-type">array </span>';
|
||||
}
|
||||
} elseif ($parameter->getClass() instanceof \ReflectionClass) {
|
||||
if ($plainText) {
|
||||
$parameterDump .= self::ansiEscapeWrap($parameter->getClass()->name . ' ', '36', $ansiColors);
|
||||
} else {
|
||||
$parameterDump .= '<span class="extbase-debug-type">'
|
||||
. htmlspecialchars($parameter->getClass()->name, ENT_COMPAT) . '</span>';
|
||||
}
|
||||
}
|
||||
if ($parameter->isPassedByReference()) {
|
||||
$parameterDump .= '&';
|
||||
}
|
||||
if ($parameter->isVariadic()) {
|
||||
$parameterDump .= '...';
|
||||
}
|
||||
if ($plainText) {
|
||||
$parameterDump .= self::ansiEscapeWrap('$' . $parameter->name, '37', $ansiColors);
|
||||
} else {
|
||||
$parameterDump .= '<span class="extbase-debug-property">'
|
||||
. htmlspecialchars('$' . $parameter->name, ENT_COMPAT) . '</span>';
|
||||
}
|
||||
if ($parameter->isDefaultValueAvailable()) {
|
||||
$parameterDump .= ' = ';
|
||||
if ($plainText) {
|
||||
$parameterDump .= self::ansiEscapeWrap(var_export($parameter->getDefaultValue(), true), '33', $ansiColors);
|
||||
} else {
|
||||
$parameterDump .= '<span class="extbase-debug-string">'
|
||||
. htmlspecialchars(var_export($parameter->getDefaultValue(), true), ENT_COMPAT) . '</span>';
|
||||
}
|
||||
}
|
||||
$params[] = $parameterDump;
|
||||
}
|
||||
$dump .= implode(', ', $params);
|
||||
if ($plainText) {
|
||||
$dump .= self::ansiEscapeWrap(') {' . PHP_EOL, '33', $ansiColors);
|
||||
} else {
|
||||
$dump .= '<span class="extbase-debug-closure">) {' . PHP_EOL . '</span>';
|
||||
}
|
||||
$lines = (array)file((string)$reflectionFunction->getFileName());
|
||||
for ($l = (int)$reflectionFunction->getStartLine(); $l < (int)$reflectionFunction->getEndLine() - 1; ++$l) {
|
||||
$line = (string)($lines[$l] ?? '');
|
||||
$dump .= $plainText ? $line : htmlspecialchars($line, ENT_COMPAT);
|
||||
}
|
||||
$dump .= str_repeat(self::PLAINTEXT_INDENT, $level);
|
||||
if ($plainText) {
|
||||
$dump .= self::ansiEscapeWrap('}' . PHP_EOL, '33', $ansiColors);
|
||||
} else {
|
||||
$dump .= '<span class="extbase-debug-closure">}</span>';
|
||||
}
|
||||
} else {
|
||||
if (get_class($object) === \stdClass::class) {
|
||||
$objReflection = new \ReflectionObject($object);
|
||||
$properties = $objReflection->getProperties();
|
||||
$methods = [];
|
||||
} else {
|
||||
$classReflection = new \ReflectionClass(get_class($object));
|
||||
$properties = $classReflection->getProperties();
|
||||
$methods = $classReflection->getMethods();
|
||||
}
|
||||
foreach ($properties as $property) {
|
||||
if (self::isBlacklisted($property) || $property->isPublic() !== true) {
|
||||
continue;
|
||||
}
|
||||
$dump .= PHP_EOL . str_repeat(self::PLAINTEXT_INDENT, $level);
|
||||
if ($plainText) {
|
||||
$dump .= self::ansiEscapeWrap($property->getName(), '37', $ansiColors);
|
||||
} else {
|
||||
$dump .= '<span class="extbase-debug-property">'
|
||||
. htmlspecialchars($property->getName(), ENT_COMPAT) . '</span>';
|
||||
}
|
||||
$dump .= ' => ';
|
||||
$property->setAccessible(true);
|
||||
$visibility = ($property->isProtected() ? 'protected' : ($property->isPrivate() ? 'private' : 'public'));
|
||||
if ($plainText) {
|
||||
$dump .= self::ansiEscapeWrap($visibility, '42;30', $ansiColors) . ' ';
|
||||
} else {
|
||||
$dump .= '<span class="extbase-debug-visibility">' . $visibility . '</span>';
|
||||
}
|
||||
$dump .= self::renderDump($property->getValue($object), $level, $plainText, $ansiColors);
|
||||
if ($object instanceof AbstractDomainObject && !$object->_isNew() && $object->_isDirty($property->getName())) {
|
||||
if ($plainText) {
|
||||
$dump .= ' ' . self::ansiEscapeWrap('modified', '43;30', $ansiColors);
|
||||
} else {
|
||||
$dump .= '<span class="extbase-debug-dirty">modified</span>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($methods as $method) {
|
||||
if (self::exposeMethod($method) === false) {
|
||||
continue;
|
||||
}
|
||||
$dump .= PHP_EOL . str_repeat(self::PLAINTEXT_INDENT, $level);
|
||||
if ($plainText) {
|
||||
$dump .= self::ansiEscapeWrap(self::shortenMethodName($method->getName()), '37', $ansiColors);
|
||||
} else {
|
||||
$dump .= '<span class="extbase-debug-property">'
|
||||
. htmlspecialchars(self::shortenMethodName($method->getName()), ENT_COMPAT) . '</span>';
|
||||
}
|
||||
$dump .= ' => ';
|
||||
$visibility = ($method->isProtected() ? 'protected' : ($method->isPrivate() ? 'private' : 'public'));
|
||||
if ($plainText) {
|
||||
$dump .= self::ansiEscapeWrap($visibility, '42;30', $ansiColors) . ' ';
|
||||
} else {
|
||||
$dump .= '<span class="extbase-debug-visibility">' . $visibility . '</span>';
|
||||
}
|
||||
$dump .= self::renderDump($method->invoke($object), $level, $plainText, $ansiColors);
|
||||
if ($object instanceof AbstractDomainObject && !$object->_isNew() && $object->_isDirty($method->getName())) {
|
||||
if ($plainText) {
|
||||
$dump .= ' ' . self::ansiEscapeWrap('modified', '43;30', $ansiColors);
|
||||
} else {
|
||||
$dump .= '<span class="extbase-debug-dirty">modified</span>';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $dump;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param iterable $collection
|
||||
* @param int $level
|
||||
* @param bool $plainText
|
||||
* @param bool $ansiColors
|
||||
* @return string
|
||||
*/
|
||||
protected static function renderCollection(iterable $collection, int $level, bool $plainText, bool $ansiColors): string
|
||||
{
|
||||
$dump = '';
|
||||
foreach ($collection as $key => $value) {
|
||||
$key = (string)$key;
|
||||
|
||||
$dump .= PHP_EOL . str_repeat(self::PLAINTEXT_INDENT, $level);
|
||||
if ($plainText) {
|
||||
$dump .= self::ansiEscapeWrap($key, '37', $ansiColors);
|
||||
} else {
|
||||
$dump .= '<span class="extbase-debug-property">' . htmlspecialchars($key, ENT_COMPAT) . '</span>';
|
||||
}
|
||||
$dump .= ' => ';
|
||||
$dump .= self::renderDump($value, $level, $plainText, $ansiColors);
|
||||
}
|
||||
if ($collection instanceof \Iterator && !$collection instanceof \Generator) {
|
||||
$collection->rewind();
|
||||
}
|
||||
return $dump;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap a string with the ANSI escape sequence for colorful output
|
||||
*
|
||||
* @param string $string The string to wrap
|
||||
* @param string $ansiColors The ansi color sequence (e.g. "1;37")
|
||||
* @param bool $enable If FALSE, the raw string will be returned
|
||||
* @return string The wrapped or raw string
|
||||
*/
|
||||
protected static function ansiEscapeWrap(string $string, string $ansiColors, bool $enable = true): string
|
||||
{
|
||||
if ($enable) {
|
||||
return '[' . $ansiColors . 'm' . $string . '[0m';
|
||||
}
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A var_dump function optimized for Extbase's object structures
|
||||
*
|
||||
* @param mixed $variable The value to dump
|
||||
* @param string $title optional custom title for the debug output
|
||||
* @param int $maxDepth Sets the max recursion depth of the dump. De- or increase the number according to your needs and memory limit.
|
||||
* @param bool $plainText If TRUE, the dump is in plain text, if FALSE the debug output is in HTML format.
|
||||
* @param bool $ansiColors If TRUE (default), ANSI color codes is added to the output, if FALSE the debug output not colored.
|
||||
* @param bool $return if TRUE, the dump is returned for custom post-processing (e.g. embed in custom HTML). If FALSE (default), the dump is directly displayed.
|
||||
* @param array $blacklistedClassNames An array of class names (RegEx) to be filtered. Default is an array of some common class names.
|
||||
* @param array $blacklistedPropertyNames An array of property names and/or array keys (RegEx) to be filtered. Default is an array of some common property names.
|
||||
* @return string if $return is TRUE, the dump is returned. By default, the dump is directly displayed, and nothing is returned.
|
||||
*/
|
||||
public static function var_dump(
|
||||
$variable,
|
||||
string $title = null,
|
||||
int $maxDepth = 8,
|
||||
bool $plainText = false,
|
||||
bool $ansiColors = true,
|
||||
bool $return = false,
|
||||
array $blacklistedClassNames = null,
|
||||
array $blacklistedPropertyNames = null
|
||||
): string {
|
||||
self::$maxDepth = $maxDepth;
|
||||
if ($title === null) {
|
||||
$title = 'Extbase Variable Dump';
|
||||
}
|
||||
$ansiColors = $plainText && $ansiColors;
|
||||
if ($ansiColors === true) {
|
||||
$title = '[1m' . $title . '[0m';
|
||||
}
|
||||
$backupBlacklistedClassNames = self::$blacklistedClassNames;
|
||||
if (is_array($blacklistedClassNames)) {
|
||||
self::$blacklistedClassNames = $blacklistedClassNames;
|
||||
}
|
||||
$backupBlacklistedPropertyNames = self::$blacklistedPropertyNames;
|
||||
if (is_array($blacklistedPropertyNames)) {
|
||||
self::$blacklistedPropertyNames = $blacklistedPropertyNames;
|
||||
}
|
||||
self::clearState();
|
||||
$css = '';
|
||||
if (!$plainText && self::$stylesheetEchoed === false) {
|
||||
$css = '
|
||||
<style type=\'text/css\'>
|
||||
.extbase-debugger-tree{position:relative}
|
||||
.extbase-debugger-tree input{position:absolute !important;float: none !important;top:0;left:0;height:14px;width:14px;margin:0 !important;cursor:pointer;opacity:0;z-index:2}
|
||||
.extbase-debugger-tree input~.extbase-debug-content{display:none}
|
||||
.extbase-debugger-tree .extbase-debug-header:before{position:relative;top:3px;content:"";padding:0;line-height:10px;height:12px;width:12px;text-align:center;margin:0 3px 0 0;background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkViZW5lXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMTIgMTIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDEyIDEyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHN0eWxlIHR5cGU9InRleHQvY3NzIj4uc3Qwe2ZpbGw6Izg4ODg4ODt9PC9zdHlsZT48cGF0aCBpZD0iQm9yZGVyIiBjbGFzcz0ic3QwIiBkPSJNMTEsMTFIMFYwaDExVjExeiBNMTAsMUgxdjloOVYxeiIvPjxnIGlkPSJJbm5lciI+PHJlY3QgeD0iMiIgeT0iNSIgY2xhc3M9InN0MCIgd2lkdGg9IjciIGhlaWdodD0iMSIvPjxyZWN0IHg9IjUiIHk9IjIiIGNsYXNzPSJzdDAiIHdpZHRoPSIxIiBoZWlnaHQ9IjciLz48L2c+PC9zdmc+);display:inline-block}
|
||||
.extbase-debugger-tree input:checked~.extbase-debug-content{display:inline}
|
||||
.extbase-debugger-tree input:checked~.extbase-debug-header:before{background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkViZW5lXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMTIgMTIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDEyIDEyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHN0eWxlIHR5cGU9InRleHQvY3NzIj4uc3Qwe2ZpbGw6Izg4ODg4ODt9PC9zdHlsZT48cGF0aCBpZD0iQm9yZGVyIiBjbGFzcz0ic3QwIiBkPSJNMTEsMTFIMFYwaDExVjExeiBNMTAsMUgxdjloOVYxeiIvPjxnIGlkPSJJbm5lciI+PHJlY3QgeD0iMiIgeT0iNSIgY2xhc3M9InN0MCIgd2lkdGg9IjciIGhlaWdodD0iMSIvPjwvZz48L3N2Zz4=)}
|
||||
.extbase-debugger{display:block;text-align:left;background:#2a2a2a;border:1px solid #2a2a2a;box-shadow:0 3px 0 rgba(0,0,0,.5);color:#000;margin:20px;overflow:hidden;border-radius:4px}
|
||||
.extbase-debugger-floating{position:relative;z-index:999}
|
||||
.extbase-debugger-top{background:#444;font-size:12px;font-family:monospace;color:#f1f1f1;padding:6px 15px}
|
||||
.extbase-debugger-center{padding:0 15px;margin:15px 0;background-image:repeating-linear-gradient(to bottom,transparent 0,transparent 20px,#252525 20px,#252525 40px)}
|
||||
.extbase-debugger-center,.extbase-debugger-center .extbase-debug-string,.extbase-debugger-center a,.extbase-debugger-center p,.extbase-debugger-center pre,.extbase-debugger-center strong{font-size:12px;font-weight:400;font-family:monospace;line-height:20px;color:#f1f1f1}
|
||||
.extbase-debugger-center pre{background-color:transparent;margin:0;padding:0;border:0;word-wrap:break-word;color:#999}
|
||||
.extbase-debugger-center .extbase-debug-string{color:#ce9178;white-space:normal}
|
||||
.extbase-debugger-center .extbase-debug-type{color:#569CD6;padding-right:4px}
|
||||
.extbase-debugger-center .extbase-debug-unregistered{background-color:#dce1e8}
|
||||
.extbase-debugger-center .extbase-debug-filtered,.extbase-debugger-center .extbase-debug-proxy,.extbase-debugger-center .extbase-debug-ptype,.extbase-debugger-center .extbase-debug-visibility,.extbase-debugger-center .extbase-debug-scope{color:#fff;font-size:10px;line-height:12px;padding:2px 4px;margin-right:2px;position:relative;top:-1px}
|
||||
.extbase-debugger-center .extbase-debug-scope{background-color:#497AA2}
|
||||
.extbase-debugger-center .extbase-debug-ptype{background-color:#698747}
|
||||
.extbase-debugger-center .extbase-debug-visibility{background-color:#698747}
|
||||
.extbase-debugger-center .extbase-debug-dirty{background-color:#FFFFB6}
|
||||
.extbase-debugger-center .extbase-debug-filtered{background-color:#4F4F4F}
|
||||
.extbase-debugger-center .extbase-debug-seeabove{text-decoration:none;font-style:italic}
|
||||
.extbase-debugger-center .extbase-debug-property{color:#f1f1f1}
|
||||
.extbase-debugger-center .extbase-debug-closure{color:#9BA223;}
|
||||
</style>';
|
||||
self::$stylesheetEchoed = true;
|
||||
}
|
||||
if ($plainText) {
|
||||
$output = $title . PHP_EOL . self::renderDump($variable, 0, true, $ansiColors) . PHP_EOL . PHP_EOL;
|
||||
} else {
|
||||
$output = '
|
||||
<div class="extbase-debugger ' . ($return ? 'extbase-debugger-inline' : 'extbase-debugger-floating') . '">
|
||||
<div class="extbase-debugger-top">' . htmlspecialchars($title, ENT_COMPAT) . '</div>
|
||||
<div class="extbase-debugger-center">
|
||||
<pre dir="ltr">' . self::renderDump($variable, 0, false, false) . '</pre>
|
||||
</div>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
self::$blacklistedClassNames = $backupBlacklistedClassNames;
|
||||
self::$blacklistedPropertyNames = $backupBlacklistedPropertyNames;
|
||||
if ($return === true) {
|
||||
return $css . $output;
|
||||
}
|
||||
echo $css . $output;
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
protected static function exposeMethod(ReflectionMethod $method): bool
|
||||
{
|
||||
$methodName = $method->getName();
|
||||
$allowedPrefixes = ['get', 'has', 'is'];
|
||||
|
||||
$allowedByName = false;
|
||||
foreach ($allowedPrefixes as $allowedPrefix) {
|
||||
if (StringUtility::beginsWith($methodName, $allowedPrefix)) {
|
||||
$allowedByName = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
$allowedByName
|
||||
&& $method->isPublic()
|
||||
&& count($method->getParameters()) === 0
|
||||
;
|
||||
}
|
||||
|
||||
protected static function shortenMethodName(string $methodName): string
|
||||
{
|
||||
$allowedPrefixes = ['get', 'has', 'is'];
|
||||
|
||||
foreach ($allowedPrefixes as $allowedPrefix) {
|
||||
if (StringUtility::beginsWith($methodName, $allowedPrefix)) {
|
||||
return lcfirst(substr($methodName, strlen($allowedPrefix)));
|
||||
}
|
||||
}
|
||||
|
||||
return $methodName;
|
||||
}
|
||||
}
|
44
Classes/ViewHelpers/DebugViewHelper.php
Normal file
44
Classes/ViewHelpers/DebugViewHelper.php
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace WerkraumMedia\FDebug\ViewHelpers;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2021 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Fluid\ViewHelpers\DebugViewHelper as Typo3DebugViewHelper;
|
||||
use WerkraumMedia\FDebug\Utility\DebuggerUtility;
|
||||
|
||||
/**
|
||||
* Replacement for original TYPO3 ViewHelper.
|
||||
* Will use alternative DebuggerUtility for integrator optimized debugging information.
|
||||
*/
|
||||
class DebugViewHelper extends Typo3DebugViewHelper
|
||||
{
|
||||
/**
|
||||
* @param array $arguments
|
||||
* @param \Closure $renderChildrenClosure
|
||||
* @param RenderingContextInterface $renderingContext
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext)
|
||||
{
|
||||
return DebuggerUtility::var_dump($renderChildrenClosure(), $arguments['title'], $arguments['maxDepth'], (bool)$arguments['plainText'], (bool)$arguments['ansiColors'], (bool)$arguments['inline'], $arguments['blacklistedClassNames'], $arguments['blacklistedPropertyNames']);
|
||||
}
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
# fdebug
|
||||
Integrator optimized f:debug alternative
|
50
Readme.rst
Normal file
50
Readme.rst
Normal file
|
@ -0,0 +1,50 @@
|
|||
TYPO3 f:debug alternative
|
||||
=========================
|
||||
|
||||
TYPO3 offers an ``f:debug`` ViewHelper to introspect variables.
|
||||
That ViewHelper is a small wrapper around ``DebuggerUtility`` of EXT:extbase.
|
||||
|
||||
The result of ``DebuggerUtility`` is always developer focused.
|
||||
``f:debug`` on the other hand is used within Fluid and should be integrator focused.
|
||||
|
||||
This extension provides an alternative to existing ``f:debug`` which focuses on
|
||||
integrators.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Run ``composer req --dev werkraummedia/fdebug:^1.0``.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
1:1 the original ``f:debug``, no need to change anything.
|
||||
The original ViewHelper is overloaded and not usable anymore.
|
||||
|
||||
The goal
|
||||
--------
|
||||
|
||||
Improve usage for integrators. E.g. show data they have access to within Fluid.
|
||||
Don't focus on properties, but actual available info.
|
||||
Inspect methods and public properties. Respect method names.
|
||||
|
||||
This should become part of TYPO3 core (by providing a patch) once it is stable
|
||||
enough.
|
||||
Right now it might lack features or break under some circumstances.
|
||||
|
||||
Please give it a try and open issues or provide pull requests.
|
||||
|
||||
Why an extension?
|
||||
-----------------
|
||||
|
||||
That allows for usage of ``ext_localconf.php`` to overload Fluid namespace ``f``.
|
||||
Also it allows to easily share and update the current state and prevent inclusion
|
||||
into production systems.
|
||||
|
||||
Current features
|
||||
----------------
|
||||
|
||||
Do not render protected or private properties. These are not available within Fluid.
|
||||
|
||||
Render methods which are public and start with ``get``, ``has`` or ``is`` and don't
|
||||
need any arguments.
|
48
composer.json
Normal file
48
composer.json
Normal file
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"name": "werkraummedia/fdebug",
|
||||
"description": "Integrator optimized f:debug alternative",
|
||||
"type": "typo3-cms-extension",
|
||||
"license": "GPL-2.0-or-later",
|
||||
"homepage": "https://github.com/werkraum-media/fdebug",
|
||||
"support": {
|
||||
"email": "coding@daniel-siepmann.de",
|
||||
"source": "https://github.com/werkraum-media/fdebug",
|
||||
"issues": "https://github.com/werkraum-media/fdebug/issues"
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "Daniel Siepmann",
|
||||
"email": "coding@daniel-siepmann.de"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"WerkraumMedia\\FDebug\\": "Classes/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"WerkraumMedia\\FDebug\\Tests\\": "Tests/"
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2",
|
||||
"typo3/cms-core": "^10.4",
|
||||
"typo3/cms-extbase": "^10.4",
|
||||
"typo3/cms-fluid": "^10.4",
|
||||
"typo3/cms-frontend": "^10.4"
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
},
|
||||
"extra": {
|
||||
"typo3/cms": {
|
||||
"cms-package-dir": "{$vendor-dir}/typo3/cms",
|
||||
"extension-key": "fdebug",
|
||||
"web-dir": ".Build/web"
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-main": "1.0.x-dev"
|
||||
}
|
||||
}
|
||||
}
|
25
ext_emconf.php
Normal file
25
ext_emconf.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
$EM_CONF[$_EXTKEY] = [
|
||||
'title' => 'f:debug Alternative',
|
||||
'description' => 'Integrator optimized f:debug alternative',
|
||||
'category' => 'fe',
|
||||
'state' => 'beta',
|
||||
'uploadfolder' => 0,
|
||||
'createDirs' => '',
|
||||
'clearCacheOnLoad' => 0,
|
||||
'author' => 'Daniel Siepmann',
|
||||
'author_email' => 'coding@daniel-siepmann.de',
|
||||
'author_company' => '',
|
||||
'version' => '1.0.0',
|
||||
'constraints' => [
|
||||
'depends' => [
|
||||
'extbase' => '',
|
||||
'fluid' => '',
|
||||
'frontend' => '',
|
||||
'core' => '',
|
||||
],
|
||||
'conflicts' => [],
|
||||
'suggests' => [],
|
||||
],
|
||||
];
|
6
ext_localconf.php
Normal file
6
ext_localconf.php
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
|
||||
defined('TYPO3') or die();
|
||||
|
||||
$GLOBALS['TYPO3_CONF_VARS']['SYS']['fluid']['namespaces']['f']['fdebug'] = 'WerkraumMedia\FDebug\ViewHelpers';
|
||||
|
Loading…
Reference in a new issue