diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..51313d0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/.Build/ +/composer.lock +/vendor/ diff --git a/Classes/Utility/DebuggerUtility.php b/Classes/Utility/DebuggerUtility.php new file mode 100644 index 0000000..d863acd --- /dev/null +++ b/Classes/Utility/DebuggerUtility.php @@ -0,0 +1,675 @@ + + * + * 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('\'%s\' (%s chars)', implode('
' . 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 = 'array'; + } + $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 .= 'max depth'; + } + } else { + $content = self::renderCollection($array, $level, $plainText, $ansiColors); + if (!$plainText) { + $header = ($level > 1 && $count > 0 ? '' : '') . $header . ''; + } + } + if ($level > 1 && $count > 0 && !$plainText) { + $dump = '' . $header . '' . $content . ''; + } 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 '' . $header . '' . $content . ''; + } + + /** + * 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 '' . $header . '' . $content . ''; + } + + /** + * 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 .= '' . htmlspecialchars($className, ENT_COMPAT) . ''; + } + if (!$object instanceof \Closure) { + if ($object instanceof SingletonInterface) { + $scope = 'singleton'; + } else { + $scope = 'prototype'; + } + if ($plainText) { + $dump .= ' ' . self::ansiEscapeWrap($scope, '44;37', $ansiColors); + } else { + $dump .= '' . $scope . ''; + } + 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 .= '' . $persistenceType . $domainObjectType . ''; + } + } + if (strpos(implode('|', self::$blacklistedClassNames), get_class($object)) > 0) { + if ($plainText) { + $dump .= ' ' . self::ansiEscapeWrap('filtered', '47;30', $ansiColors); + } else { + $dump .= 'filtered'; + } + } elseif (self::$renderedObjects->contains($object) && !$plainText) { + $dump = '' . $dump . 'see above'; + } elseif ($level >= self::$maxDepth && !$object instanceof \DateTimeInterface) { + if ($plainText) { + $dump .= ' ' . self::ansiEscapeWrap('max depth', '47;30', $ansiColors); + } else { + $dump .= 'max depth'; + } + } elseif ($level > 1 && !$object instanceof \DateTimeInterface && !$plainText) { + if (($object instanceof \Countable && empty($object)) || empty($classReflection->getProperties())) { + $dump = '' . $dump . ''; + } else { + $dump = '' . $dump . ''; + } + } + 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 .= ''; + } + if ($object instanceof \Closure) { + $dump .= PHP_EOL . str_repeat(self::PLAINTEXT_INDENT, $level) + . ($plainText ? '' : '') + . self::ansiEscapeWrap('function (', '33', $ansiColors) . ($plainText ? '' : ''); + + $reflectionFunction = new \ReflectionFunction($object); + $params = []; + foreach ($reflectionFunction->getParameters() as $parameter) { + $parameterDump = ''; + if ($parameter->isArray()) { + if ($plainText) { + $parameterDump .= self::ansiEscapeWrap('array ', '36', $ansiColors); + } else { + $parameterDump .= 'array '; + } + } elseif ($parameter->getClass() instanceof \ReflectionClass) { + if ($plainText) { + $parameterDump .= self::ansiEscapeWrap($parameter->getClass()->name . ' ', '36', $ansiColors); + } else { + $parameterDump .= '' + . htmlspecialchars($parameter->getClass()->name, ENT_COMPAT) . ''; + } + } + if ($parameter->isPassedByReference()) { + $parameterDump .= '&'; + } + if ($parameter->isVariadic()) { + $parameterDump .= '...'; + } + if ($plainText) { + $parameterDump .= self::ansiEscapeWrap('$' . $parameter->name, '37', $ansiColors); + } else { + $parameterDump .= '' + . htmlspecialchars('$' . $parameter->name, ENT_COMPAT) . ''; + } + if ($parameter->isDefaultValueAvailable()) { + $parameterDump .= ' = '; + if ($plainText) { + $parameterDump .= self::ansiEscapeWrap(var_export($parameter->getDefaultValue(), true), '33', $ansiColors); + } else { + $parameterDump .= '' + . htmlspecialchars(var_export($parameter->getDefaultValue(), true), ENT_COMPAT) . ''; + } + } + $params[] = $parameterDump; + } + $dump .= implode(', ', $params); + if ($plainText) { + $dump .= self::ansiEscapeWrap(') {' . PHP_EOL, '33', $ansiColors); + } else { + $dump .= ') {' . PHP_EOL . ''; + } + $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 .= '}'; + } + } 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 .= '' + . htmlspecialchars($property->getName(), ENT_COMPAT) . ''; + } + $dump .= ' => '; + $property->setAccessible(true); + $visibility = ($property->isProtected() ? 'protected' : ($property->isPrivate() ? 'private' : 'public')); + if ($plainText) { + $dump .= self::ansiEscapeWrap($visibility, '42;30', $ansiColors) . ' '; + } else { + $dump .= '' . $visibility . ''; + } + $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 .= 'modified'; + } + } + } + + 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 .= '' + . htmlspecialchars(self::shortenMethodName($method->getName()), ENT_COMPAT) . ''; + } + $dump .= ' => '; + $visibility = ($method->isProtected() ? 'protected' : ($method->isPrivate() ? 'private' : 'public')); + if ($plainText) { + $dump .= self::ansiEscapeWrap($visibility, '42;30', $ansiColors) . ' '; + } else { + $dump .= '' . $visibility . ''; + } + $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 .= 'modified'; + } + } + } + } + } + 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 .= '' . htmlspecialchars($key, ENT_COMPAT) . ''; + } + $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 = ' + '; + self::$stylesheetEchoed = true; + } + if ($plainText) { + $output = $title . PHP_EOL . self::renderDump($variable, 0, true, $ansiColors) . PHP_EOL . PHP_EOL; + } else { + $output = ' +
+
' . htmlspecialchars($title, ENT_COMPAT) . '
+
+
' . self::renderDump($variable, 0, false, false) . '
+
+
+ '; + } + 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; + } +} diff --git a/Classes/ViewHelpers/DebugViewHelper.php b/Classes/ViewHelpers/DebugViewHelper.php new file mode 100644 index 0000000..9798c48 --- /dev/null +++ b/Classes/ViewHelpers/DebugViewHelper.php @@ -0,0 +1,44 @@ + + * + * 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']); + } +} diff --git a/README.md b/README.md deleted file mode 100644 index ae6e6f4..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# fdebug -Integrator optimized f:debug alternative diff --git a/Readme.rst b/Readme.rst new file mode 100644 index 0000000..cb86999 --- /dev/null +++ b/Readme.rst @@ -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. diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..dfe9ec9 --- /dev/null +++ b/composer.json @@ -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" + } + } +} diff --git a/ext_emconf.php b/ext_emconf.php new file mode 100644 index 0000000..01343b8 --- /dev/null +++ b/ext_emconf.php @@ -0,0 +1,25 @@ + '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' => [], + ], +]; diff --git a/ext_localconf.php b/ext_localconf.php new file mode 100644 index 0000000..55b1b25 --- /dev/null +++ b/ext_localconf.php @@ -0,0 +1,6 @@ +