First basic version

This commit is contained in:
Daniel Siepmann 2021-05-10 13:43:23 +02:00
parent 81d4a787f8
commit def53b593a
Signed by: Daniel Siepmann
GPG key ID: 33D6629915560EF4
8 changed files with 851 additions and 2 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
/.Build/
/composer.lock
/vendor/

View 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 = '&nbsp;&nbsp;&nbsp;';
/**
* @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 . '';
}
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 = '' . $title . '';
}
$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;
}
}

View 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']);
}
}

View file

@ -1,2 +0,0 @@
# fdebug
Integrator optimized f:debug alternative

50
Readme.rst Normal file
View 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
View 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
View 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
View file

@ -0,0 +1,6 @@
<?php
defined('TYPO3') or die();
$GLOBALS['TYPO3_CONF_VARS']['SYS']['fluid']['namespaces']['f']['fdebug'] = 'WerkraumMedia\FDebug\ViewHelpers';