├── Classes ├── Cache │ ├── CacheFactory.php │ └── ClassCacheManager.php ├── Command │ └── ClearCommand.php ├── Composer │ ├── ClassComposer.php │ └── Generator │ │ ├── ClassGenerator.php │ │ ├── ClassMethodGenerator.php │ │ ├── ConstantGenerator.php │ │ ├── ConstructorGenerator.php │ │ ├── FileCommentGenerator.php │ │ ├── GeneratorInterface.php │ │ ├── InitializeObjectGenerator.php │ │ ├── NamespaceGenerator.php │ │ ├── PropertyGenerator.php │ │ ├── TraitGenerator.php │ │ └── UseGenerator.php ├── Configuration │ └── ClassRegister.php ├── DependencyInjection │ └── RegisterExtenderPass.php ├── Event │ └── RegisterAutoloaderEvent.php ├── Exception │ ├── BaseFileNotFoundException.php │ └── ExtendingFileNotFoundException.php ├── Hooks │ └── ClearCacheHook.php ├── Loader │ └── ClassLoader.php └── Parser │ ├── ClassParser.php │ ├── FileSegments.php │ └── Visitor │ ├── AbstractVisitor.php │ ├── ClassMethodVisitor.php │ ├── ClassVisitor.php │ ├── ConstantVisitor.php │ ├── ConstructorVisitor.php │ ├── InitializeObjectVisitor.php │ ├── NamespaceVisitor.php │ ├── PropertyVisitor.php │ ├── TraitVisitor.php │ └── UseVisitor.php ├── Configuration ├── Services.php └── Services.yaml ├── Documentation ├── Breaking │ └── Index.rst ├── Configuration │ └── Index.rst ├── Includes.rst.txt ├── Index.rst ├── Installation │ └── Index.rst ├── Introduction │ ├── Images │ │ ├── base.png │ │ ├── extend.png │ │ └── merged.png │ └── Index.rst ├── Sitemap.rst ├── Testing │ ├── Images │ │ └── phpstorm_configuration.png │ └── Index.rst └── guides.xml ├── LICENSE.txt ├── README.md ├── Resources └── Public │ └── Icons │ └── Extension.svg ├── composer.json ├── ext_emconf.php └── ext_localconf.php /Classes/Cache/CacheFactory.php: -------------------------------------------------------------------------------- 1 | > 29 | */ 30 | protected static array $configuration = [ 31 | 'frontend' => PhpFrontend::class, 32 | 'backend' => FileBackend::class, 33 | 'groups' => [ 34 | 'all', 35 | 'system', 36 | ], 37 | 'options' => [ 38 | 'defaultLifetime' => AbstractBackend::UNLIMITED_LIFETIME, 39 | ], 40 | ]; 41 | 42 | public function createCache(string $identifier): ?FrontendInterface 43 | { 44 | self::addClassCacheConfigToGlobalTypo3ConfVars(); 45 | try { 46 | $cache = Bootstrap::createCache($identifier); 47 | } catch (\Exception) { 48 | $cache = null; 49 | } 50 | return $cache; 51 | } 52 | 53 | public static function addClassCacheConfigToGlobalTypo3ConfVars(): void 54 | { 55 | $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['extender'] = static::$configuration; 56 | $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['clearCachePostProc']['extender'] = 57 | ClearCacheHook::class . '->clearCachePostProc'; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Classes/Cache/ClassCacheManager.php: -------------------------------------------------------------------------------- 1 | getBaseClassFileSegments($className), 47 | ...$this->getExtendingClassesFileSegments($className), 48 | ]; 49 | 50 | $code = $this->getMergedFileCode($fileSegments); 51 | $this->addFileToCache($cacheEntryIdentifier, $code); 52 | } 53 | 54 | protected function getBaseClassFileSegments(string $className): FileSegments 55 | { 56 | return $this->getFileSegments($className, true, BaseFileNotFoundException::class); 57 | } 58 | 59 | /** 60 | * @return FileSegments[] 61 | */ 62 | protected function getExtendingClassesFileSegments(string $baseClassName): array 63 | { 64 | $filesSegments = []; 65 | 66 | foreach ($this->classRegister->getExtendingClasses($baseClassName) as $className) { 67 | $filesSegments[] = $this->getFileSegments($className, false, ExtendingFileNotFoundException::class); 68 | } 69 | 70 | return $filesSegments; 71 | } 72 | 73 | protected function getFileSegments(string $className, bool $baseClass, string $exceptionClass): FileSegments 74 | { 75 | $type = $baseClass ? 'base' : 'extend'; 76 | $filePath = $this->classLoader->findFile($className); 77 | $filePath = realpath($filePath); 78 | 79 | if ($filePath === false) { 80 | throw new $exceptionClass( 81 | 'Composer did not find the file path for ' . $type . ' class "' . $className . '"' 82 | ); 83 | } 84 | 85 | if (!is_file($filePath)) { 86 | throw new $exceptionClass( 87 | 'File "' . $filePath . '" for ' . $type . ' class "' . $className . '" does not exist' 88 | ); 89 | } 90 | 91 | $fileSegments = $this->classParser->getFileSegments($filePath); 92 | $fileSegments->setBaseClass($baseClass); 93 | 94 | return $fileSegments; 95 | } 96 | 97 | /** 98 | * @param FileSegments[] $fileSegments 99 | */ 100 | protected function getMergedFileCode(array $fileSegments): string 101 | { 102 | return $this->classComposer->composeMergedFileCode($fileSegments); 103 | } 104 | 105 | protected function addFileToCache(string $cacheEntryIdentifier, string $code): void 106 | { 107 | try { 108 | $this->classCache->set($cacheEntryIdentifier, $code); 109 | } catch (\Exception) { 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Classes/Command/ClearCommand.php: -------------------------------------------------------------------------------- 1 | cacheFactory->createCache('extender')->flush(); 38 | $output->writeln('Cache cleared'); 39 | } catch (\Exception $e) { 40 | $output->writeln('' . $e->getMessage() . ''); 41 | $result = self::FAILURE; 42 | } 43 | return $result; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Classes/Composer/ClassComposer.php: -------------------------------------------------------------------------------- 1 | generators as $generator) { 49 | $statements = $this->addFileStatement($statements, $fileSegments, $generator); 50 | } 51 | 52 | $prettyPrinter = new PrettyPrinter(); 53 | $fileCode = $prettyPrinter->prettyPrintFile($statements); 54 | 55 | return str_replace('generate($statements, $fileSegments); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Classes/Composer/Generator/ClassGenerator.php: -------------------------------------------------------------------------------- 1 | getNamespace($statements); 34 | $class = $this->getClass($fileSegments); 35 | 36 | if ($class) { 37 | foreach ($fileSegments as $fileSegment) { 38 | if ($fileSegment->isBaseClass()) { 39 | continue; 40 | } 41 | $class->implements = $this->getUniqueImplements($fileSegments); 42 | } 43 | $namespace->stmts[] = $class; 44 | } 45 | 46 | return $statements; 47 | } 48 | 49 | /** 50 | * @param FileSegments[] $fileSegments 51 | * @return Name[] 52 | */ 53 | protected function getUniqueImplements(array $fileSegments): array 54 | { 55 | $implements = []; 56 | foreach ($fileSegments as $fileSegment) { 57 | /** @var Name $currentImplement */ 58 | foreach ($fileSegment->getClass()->implements as $currentImplement) { 59 | if (isset($implements[(string)$currentImplement])) { 60 | continue; 61 | } 62 | $implements[(string)$currentImplement] = $currentImplement; 63 | } 64 | } 65 | return array_values($implements); 66 | } 67 | 68 | /** 69 | * @param Node[] $statements 70 | */ 71 | protected function getNamespace(array $statements): ?Namespace_ 72 | { 73 | $namespace = null; 74 | foreach ($statements as $statement) { 75 | if ($statement instanceof Namespace_) { 76 | $namespace = $statement; 77 | break; 78 | } 79 | } 80 | return $namespace; 81 | } 82 | 83 | /** 84 | * @param FileSegments[] $fileSegments 85 | */ 86 | protected function getClass(array $fileSegments): ?Class_ 87 | { 88 | $class = null; 89 | foreach ($fileSegments as $fileSegment) { 90 | if ($fileSegment->isBaseClass() && $fileSegment->getClass()) { 91 | $class = $fileSegment->getClass(); 92 | break; 93 | } 94 | } 95 | return $class; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Classes/Composer/Generator/ClassMethodGenerator.php: -------------------------------------------------------------------------------- 1 | getNamespace($statements); 34 | $class = $this->getClass($namespace); 35 | 36 | if ($class) { 37 | $class->stmts = [...$class->stmts, ...$this->getUniqueClassMethods($fileSegments)]; 38 | } 39 | 40 | return $statements; 41 | } 42 | 43 | /** 44 | * @param FileSegments[] $fileSegments 45 | * @return ClassMethod[] 46 | */ 47 | protected function getUniqueClassMethods(array $fileSegments): array 48 | { 49 | $classMethods = []; 50 | foreach ($fileSegments as $fileSegment) { 51 | foreach ($fileSegment->getMethods() as $currentMethod) { 52 | if (isset($classMethods[(string)$currentMethod->name])) { 53 | continue; 54 | } 55 | $classMethods[(string)$currentMethod->name] = $currentMethod; 56 | } 57 | } 58 | return array_values($classMethods); 59 | } 60 | 61 | /** 62 | * @param Node[] $statements 63 | */ 64 | protected function getNamespace(array $statements): ?Namespace_ 65 | { 66 | $namespace = null; 67 | foreach ($statements as $statement) { 68 | if ($statement instanceof Namespace_) { 69 | $namespace = $statement; 70 | break; 71 | } 72 | } 73 | return $namespace; 74 | } 75 | 76 | protected function getClass(Namespace_ $namespace): ?Class_ 77 | { 78 | /** @var ?Class_ $class */ 79 | $class = null; 80 | foreach ($namespace->stmts as $node) { 81 | if ($node instanceof Class_) { 82 | $class = $node; 83 | break; 84 | } 85 | } 86 | return $class; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Classes/Composer/Generator/ConstantGenerator.php: -------------------------------------------------------------------------------- 1 | getNamespace($statements); 34 | $class = $this->getClass($namespace); 35 | 36 | if ($class) { 37 | $class->stmts = [...$class->stmts, ...$this->getUniqueClassConstants($fileSegments)]; 38 | } 39 | 40 | return $statements; 41 | } 42 | 43 | /** 44 | * @param FileSegments[] $fileSegments 45 | * @return ClassConst[] 46 | */ 47 | protected function getUniqueClassConstants(array $fileSegments): array 48 | { 49 | $classConstant = []; 50 | foreach ($fileSegments as $fileSegment) { 51 | foreach ($fileSegment->getClassConsts() as $currentClassConstant) { 52 | foreach ($currentClassConstant->consts as $currentConst) { 53 | if (isset($classConstant[(string)$currentConst->name])) { 54 | continue; 55 | } 56 | $classConstant[(string)$currentConst->name] = new ClassConst([$currentConst]); 57 | } 58 | } 59 | } 60 | return array_values($classConstant); 61 | } 62 | 63 | /** 64 | * @param Node[] $statements 65 | */ 66 | protected function getNamespace(array $statements): ?Namespace_ 67 | { 68 | $namespace = null; 69 | foreach ($statements as $statement) { 70 | if ($statement instanceof Namespace_) { 71 | $namespace = $statement; 72 | break; 73 | } 74 | } 75 | return $namespace; 76 | } 77 | 78 | protected function getClass(Namespace_ $namespace): ?Class_ 79 | { 80 | /** @var ?Class_ $class */ 81 | $class = null; 82 | foreach ($namespace->stmts as $node) { 83 | if ($node instanceof Class_) { 84 | $class = $node; 85 | break; 86 | } 87 | } 88 | return $class; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Classes/Composer/Generator/ConstructorGenerator.php: -------------------------------------------------------------------------------- 1 | getNamespace($statements); 39 | $class = $this->getClass($namespace); 40 | 41 | if ($class && $this->hasConstructor($fileSegments)) { 42 | [$params, $stmts] = $this->getParamsAndStmts($fileSegments); 43 | $class->stmts[] = new ClassMethod( 44 | '__construct', 45 | [ 46 | 'flags' => Modifiers::PUBLIC, 47 | 'params' => $params, 48 | 'stmts' => $stmts, 49 | ] 50 | ); 51 | } 52 | 53 | return $statements; 54 | } 55 | 56 | /** 57 | * @param FileSegments[] $fileSegments 58 | * @return array 59 | */ 60 | protected function getParamsAndStmts(array $fileSegments): array 61 | { 62 | $params = []; 63 | $stmts = []; 64 | foreach ($fileSegments as $fileSegment) { 65 | $constructor = $fileSegment->getConstructor(); 66 | if (!$constructor) { 67 | continue; 68 | } 69 | 70 | $params = $this->getConstructorParameter($params, $constructor->params); 71 | $stmts = $this->getConstructorStatements($stmts, $constructor->stmts, $fileSegment->isBaseClass()); 72 | } 73 | 74 | return [$params, $stmts]; 75 | } 76 | 77 | /** 78 | * @param Param[] $result 79 | * @param Param[] $params 80 | * @return Param[] 81 | */ 82 | protected function getConstructorParameter(array $result, array $params): array 83 | { 84 | foreach ($params as $param) { 85 | if (isset($result[$param->var->name])) { 86 | continue; 87 | } 88 | $result[$param->var->name] = $param; 89 | } 90 | 91 | return $result; 92 | } 93 | 94 | /** 95 | * @param Stmt[] $result 96 | * @param Stmt[]|Expression[] $stmts 97 | * @param bool $isBaseClass 98 | * @return Stmt[] 99 | */ 100 | protected function getConstructorStatements(array $result, array $stmts, bool $isBaseClass): array 101 | { 102 | if ($isBaseClass) { 103 | $result = [...$result, ...$stmts]; 104 | } else { 105 | /** @var Expression $stmt */ 106 | foreach ($stmts as $stmt) { 107 | $expr = $stmt->expr; 108 | if ( 109 | !( 110 | $expr instanceof StaticCall 111 | && (string)$expr->class === 'parent' 112 | && (string)$expr->name === '__construct' 113 | ) 114 | ) { 115 | $result[] = $stmt; 116 | } 117 | } 118 | } 119 | 120 | return $result; 121 | } 122 | 123 | /** 124 | * @param FileSegments[] $fileSegments 125 | */ 126 | protected function hasConstructor(array $fileSegments): bool 127 | { 128 | $result = false; 129 | 130 | foreach ($fileSegments as $fileSegment) { 131 | if ($fileSegment->getConstructor()) { 132 | $result = true; 133 | break; 134 | } 135 | } 136 | 137 | return $result; 138 | } 139 | 140 | /** 141 | * @param Node[] $statements 142 | */ 143 | protected function getNamespace(array $statements): ?Namespace_ 144 | { 145 | $namespace = null; 146 | foreach ($statements as $statement) { 147 | if ($statement instanceof Namespace_) { 148 | $namespace = $statement; 149 | break; 150 | } 151 | } 152 | return $namespace; 153 | } 154 | 155 | protected function getClass(Namespace_ $namespace): ?Class_ 156 | { 157 | /** @var ?Class_ $class */ 158 | $class = null; 159 | foreach ($namespace->stmts as $node) { 160 | if ($node instanceof Class_) { 161 | $class = $node; 162 | break; 163 | } 164 | } 165 | return $class; 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /Classes/Composer/Generator/FileCommentGenerator.php: -------------------------------------------------------------------------------- 1 | createCommentText($fileSegments); 27 | 28 | $nop = new Nop(); 29 | $nop->setDocComment(new Doc($commentText)); 30 | 31 | $statements[] = $nop; 32 | 33 | return $statements; 34 | } 35 | 36 | /** 37 | * @param FileSegments[] $fileSegments 38 | */ 39 | protected function createCommentText(array $fileSegments): string 40 | { 41 | $fileComment = [ 42 | '/*', 43 | ' * This file is composed by "extender"', 44 | ' * Merged class with parts of files:', 45 | ]; 46 | 47 | foreach ($fileSegments as $fileSegment) { 48 | $fileComment[] = ' * - ' . $fileSegment->getFilePath(); 49 | } 50 | 51 | $fileComment[] = ' */'; 52 | 53 | return implode(chr(10), $fileComment) . chr(10); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Classes/Composer/Generator/GeneratorInterface.php: -------------------------------------------------------------------------------- 1 | getNamespace($statements); 39 | $class = $this->getClass($namespace); 40 | 41 | if ($class && $this->hasInitializeObject($fileSegments)) { 42 | [$params, $stmts] = $this->getParamsAndStmts($fileSegments); 43 | $class->stmts[] = new ClassMethod( 44 | 'initializeObject', 45 | [ 46 | 'flags' => Modifiers::PUBLIC, 47 | 'params' => $params, 48 | 'stmts' => $stmts, 49 | ] 50 | ); 51 | } 52 | 53 | return $statements; 54 | } 55 | 56 | /** 57 | * @param FileSegments[] $fileSegments 58 | * @return array 59 | */ 60 | protected function getParamsAndStmts(array $fileSegments): array 61 | { 62 | $params = []; 63 | $stmts = []; 64 | foreach ($fileSegments as $fileSegment) { 65 | $initializeObject = $fileSegment->getInitializeObject(); 66 | if (!$initializeObject) { 67 | continue; 68 | } 69 | 70 | $params = $this->getInitializeObjectParameter($params, $initializeObject->getParams()); 71 | $stmts = $this->getInitializeObjectStatements( 72 | $stmts, 73 | $initializeObject->getStmts(), 74 | $fileSegment->isBaseClass() 75 | ); 76 | } 77 | 78 | return [$params, $stmts]; 79 | } 80 | 81 | /** 82 | * @param Param[] $result 83 | * @param Param[] $params 84 | * @return Param[] 85 | */ 86 | protected function getInitializeObjectParameter(array $result, array $params): array 87 | { 88 | foreach ($params as $param) { 89 | if (isset($result[$param->var->name])) { 90 | continue; 91 | } 92 | $result[$param->var->name] = $param; 93 | } 94 | 95 | return $result; 96 | } 97 | 98 | /** 99 | * @param Stmt[] $result 100 | * @param Stmt[]|Expression[] $stmts 101 | * @param bool $isBaseClass 102 | * @return Stmt[] 103 | */ 104 | protected function getInitializeObjectStatements(array $result, array $stmts, bool $isBaseClass): array 105 | { 106 | if ($isBaseClass) { 107 | $result = [...$result, ...$stmts]; 108 | } else { 109 | foreach ($stmts as $stmt) { 110 | /** @var Expression|StaticCall $stmt */ 111 | $expr = $stmt->expr; 112 | if ( 113 | !( 114 | $expr instanceof StaticCall 115 | && (string)$expr->class === 'parent' 116 | && (string)$expr->name === 'initializeObject' 117 | ) 118 | ) { 119 | $result[] = $stmt; 120 | } 121 | } 122 | } 123 | 124 | return $result; 125 | } 126 | 127 | /** 128 | * @param FileSegments[] $fileSegments 129 | */ 130 | protected function hasInitializeObject(array $fileSegments): bool 131 | { 132 | $result = false; 133 | foreach ($fileSegments as $fileSegment) { 134 | if ($fileSegment->getInitializeObject()) { 135 | $result = true; 136 | break; 137 | } 138 | } 139 | return $result; 140 | } 141 | 142 | /** 143 | * @param Node[] $statements 144 | */ 145 | protected function getNamespace(array $statements): ?Namespace_ 146 | { 147 | $namespace = null; 148 | foreach ($statements as $statement) { 149 | if ($statement instanceof Namespace_) { 150 | $namespace = $statement; 151 | break; 152 | } 153 | } 154 | return $namespace; 155 | } 156 | 157 | protected function getClass(Namespace_ $namespace): ?Class_ 158 | { 159 | /** @var ?Class_ $class */ 160 | $class = null; 161 | foreach ($namespace->stmts as $node) { 162 | if ($node instanceof Class_) { 163 | $class = $node; 164 | break; 165 | } 166 | } 167 | return $class; 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /Classes/Composer/Generator/NamespaceGenerator.php: -------------------------------------------------------------------------------- 1 | isBaseClass()) { 33 | continue; 34 | } 35 | $statements[] = new Namespace_($fileSegment->getNamespace()); 36 | } 37 | 38 | return $statements; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Classes/Composer/Generator/PropertyGenerator.php: -------------------------------------------------------------------------------- 1 | getNamespace($statements); 35 | $class = $this->getClass($namespace); 36 | 37 | if ($class) { 38 | $class->stmts = [...$class->stmts, ...$this->getUniqueProperties($fileSegments)]; 39 | } 40 | 41 | return $statements; 42 | } 43 | 44 | /** 45 | * @param FileSegments[] $fileSegments 46 | * @return Property[] 47 | */ 48 | protected function getUniqueProperties(array $fileSegments): array 49 | { 50 | $properties = []; 51 | foreach ($fileSegments as $fileSegment) { 52 | foreach ($fileSegment->getProperties() as $property) { 53 | if (count($property->props) == 1) { 54 | $propertyProperty = $property->props[0]; 55 | $properties[(string)$propertyProperty->name] = $property; 56 | } else { 57 | foreach ($property->props as $propertyProperty) { 58 | $properties[(string)$propertyProperty->name] = new Property( 59 | Modifiers::PROTECTED, 60 | [$propertyProperty] 61 | ); 62 | } 63 | } 64 | } 65 | } 66 | return array_values($properties); 67 | } 68 | 69 | /** 70 | * @param Node[] $statements 71 | */ 72 | protected function getNamespace(array $statements): ?Namespace_ 73 | { 74 | $namespace = null; 75 | foreach ($statements as $statement) { 76 | if ($statement instanceof Namespace_) { 77 | $namespace = $statement; 78 | break; 79 | } 80 | } 81 | return $namespace; 82 | } 83 | 84 | protected function getClass(Namespace_ $namespace): ?Class_ 85 | { 86 | /** @var ?Class_ $class */ 87 | $class = null; 88 | foreach ($namespace->stmts as $node) { 89 | if ($node instanceof Class_) { 90 | $class = $node; 91 | break; 92 | } 93 | } 94 | return $class; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Classes/Composer/Generator/TraitGenerator.php: -------------------------------------------------------------------------------- 1 | getNamespace($statements); 34 | $class = $this->getClass($namespace); 35 | 36 | if ($class) { 37 | $class->stmts = [...$class->stmts, ...$this->getUniqueTraits($fileSegments)]; 38 | } 39 | 40 | return $statements; 41 | } 42 | 43 | /** 44 | * @param FileSegments[] $fileSegments 45 | * @return TraitUse[] 46 | */ 47 | protected function getUniqueTraits(array $fileSegments): array 48 | { 49 | $traits = []; 50 | foreach ($fileSegments as $fileSegment) { 51 | foreach ($fileSegment->getTraits() as $currentTraits) { 52 | foreach ($currentTraits->traits as $currentTrait) { 53 | if (isset($traits[(string)$currentTrait])) { 54 | continue; 55 | } 56 | $traits[(string)$currentTrait] = new TraitUse([$currentTrait]); 57 | } 58 | } 59 | } 60 | return array_values($traits); 61 | } 62 | 63 | /** 64 | * @param Node[] $statements 65 | */ 66 | protected function getNamespace(array $statements): ?Namespace_ 67 | { 68 | $namespace = null; 69 | foreach ($statements as $statement) { 70 | if ($statement instanceof Namespace_) { 71 | $namespace = $statement; 72 | break; 73 | } 74 | } 75 | return $namespace; 76 | } 77 | 78 | protected function getClass(Namespace_ $namespace): ?Class_ 79 | { 80 | /** @var ?Class_ $class */ 81 | $class = null; 82 | foreach ($namespace->stmts as $node) { 83 | if ($node instanceof Class_) { 84 | $class = $node; 85 | break; 86 | } 87 | } 88 | return $class; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Classes/Composer/Generator/UseGenerator.php: -------------------------------------------------------------------------------- 1 | getNamespace($statements); 34 | 35 | $uses = $this->getUniqueUses($fileSegments); 36 | foreach ($uses as $use) { 37 | $namespace->stmts[] = new Use_([$use]); 38 | } 39 | 40 | return $statements; 41 | } 42 | 43 | /** 44 | * @param FileSegments[] $fileSegments 45 | * @return UseItem[] 46 | */ 47 | protected function getUniqueUses(array $fileSegments): array 48 | { 49 | $uses = []; 50 | foreach ($fileSegments as $fileSegment) { 51 | foreach ($fileSegment->getUses() as $use) { 52 | $name = $use->name . $use->getAlias(); 53 | if (isset($uses[$name])) { 54 | continue; 55 | } 56 | $uses[$name] = $use; 57 | } 58 | } 59 | return array_values($uses); 60 | } 61 | 62 | /** 63 | * @param Node[] $statements 64 | */ 65 | protected function getNamespace(array $statements): ?Namespace_ 66 | { 67 | $namespace = null; 68 | foreach ($statements as $statement) { 69 | if ($statement instanceof Namespace_) { 70 | $namespace = $statement; 71 | break; 72 | } 73 | } 74 | return $namespace; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Classes/Configuration/ClassRegister.php: -------------------------------------------------------------------------------- 1 | $extendedClasses 22 | */ 23 | public function __construct(protected array $extendedClasses = []) {} 24 | 25 | public function hasBaseClassName(string $className): bool 26 | { 27 | return isset($this->extendedClasses[$className]); 28 | } 29 | 30 | /** 31 | * @return string[] 32 | */ 33 | public function getExtendingClasses(string $className): array 34 | { 35 | return is_array($this->extendedClasses[$className] ?? false) 36 | ? $this->extendedClasses[$className] 37 | : []; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Classes/DependencyInjection/RegisterExtenderPass.php: -------------------------------------------------------------------------------- 1 | addRegisterAutoloadEventToEventDispatcher($container); 29 | $this->addExtendedClassesToRegisterDefinition($container); 30 | } 31 | 32 | protected function addRegisterAutoloadEventToEventDispatcher(ContainerBuilder $container): void 33 | { 34 | $registerAutoloaderEvent = $container->findDefinition(RegisterAutoloaderEvent::class); 35 | $eventDispatcher = $container->findDefinition(EventDispatcherInterface::class); 36 | $eventDispatcher->addMethodCall('dispatch', [$registerAutoloaderEvent]); 37 | } 38 | 39 | protected function addExtendedClassesToRegisterDefinition(ContainerBuilder $container): void 40 | { 41 | $extendedClasses = []; 42 | foreach ($container->findTaggedServiceIds('extender.extends', true) as $extendingClass => $tags) { 43 | foreach ($tags as $tag) { 44 | $extendedClass = $tag['class'] ?? ''; 45 | if ($extendedClass === '') { 46 | continue; 47 | } 48 | 49 | if (!isset($extendedClasses[$extendedClass])) { 50 | $extendedClasses[$extendedClass] = []; 51 | } 52 | $extendedClasses[$extendedClass][] = $extendingClass; 53 | } 54 | } 55 | 56 | $classRegisterDefinition = $container->getDefinition(ClassRegister::class); 57 | $classRegisterDefinition->setArguments([$extendedClasses]); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Classes/Event/RegisterAutoloaderEvent.php: -------------------------------------------------------------------------------- 1 | get(ClassLoader::class), 'loadClass']; 29 | if ($this->autoloaderAlreadyRegistered($autoloader)) { 30 | $this->unregisterAutoloader($autoloader); 31 | } 32 | spl_autoload_register($autoloader, true, true); 33 | } catch (ContainerExceptionInterface) { 34 | } 35 | } 36 | 37 | /** 38 | * @param array $autoloader 39 | */ 40 | protected function autoloaderAlreadyRegistered(array $autoloader): bool 41 | { 42 | $result = false; 43 | 44 | $autoloaderClass = get_class($autoloader[0]); 45 | $currentAutoLoaders = spl_autoload_functions(); 46 | foreach ($currentAutoLoaders as $currentAutoLoader) { 47 | if ( 48 | is_array($currentAutoLoader) 49 | && ( 50 | (is_object($currentAutoLoader[0]) && get_class($currentAutoLoader[0]) === $autoloaderClass) 51 | || (is_string($currentAutoLoader[0]) && $currentAutoLoader[0] === $autoloaderClass) 52 | ) 53 | ) { 54 | $result = true; 55 | break; 56 | } 57 | } 58 | 59 | return $result; 60 | } 61 | 62 | /** 63 | * @param array $autoloader 64 | */ 65 | protected function unregisterAutoloader(array $autoloader): void 66 | { 67 | spl_autoload_unregister($autoloader); 68 | } 69 | 70 | public function isPropagationStopped(): bool 71 | { 72 | return true; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Classes/Exception/BaseFileNotFoundException.php: -------------------------------------------------------------------------------- 1 | $parameters 16 | */ 17 | public function clearCachePostProc(array $parameters): void 18 | { 19 | if (Environment::getContext()->isDevelopment() && ($parameters['cacheCmd'] ?? '') === 'all') { 20 | $this->classCache->flush(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Classes/Loader/ClassLoader.php: -------------------------------------------------------------------------------- 1 | isValidClassName($className)) { 41 | $cacheEntryIdentifier = str_replace('\\', '_', $className); 42 | 43 | if (!$this->classCache->has($cacheEntryIdentifier)) { 44 | $this->classCacheManager->build($cacheEntryIdentifier, $className); 45 | } 46 | 47 | $this->classCache->requireOnce($cacheEntryIdentifier); 48 | $return = true; 49 | } 50 | 51 | return $return; 52 | } 53 | 54 | protected function isValidClassName(string $className): bool 55 | { 56 | return $this->classRegister->hasBaseClassName($className); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Classes/Parser/ClassParser.php: -------------------------------------------------------------------------------- 1 | setFilePath($filePath); 45 | $fileSegments->setCode(file_get_contents($filePath)); 46 | 47 | try { 48 | // @extensionScannerIgnoreLine 49 | // @phpstan-ignore method.notFound 50 | $parser = $this->parserFactory->createForVersion(PhpVersion::fromComponents(8, 2)); 51 | $fileSegments->setStatements($parser->parse($fileSegments->getCode())); 52 | 53 | foreach ($this->visitors as $visitor) { 54 | $this->traverseStatements($fileSegments, $visitor); 55 | } 56 | } catch (\Exception) { 57 | } 58 | 59 | return $fileSegments; 60 | } 61 | 62 | protected function traverseStatements(FileSegments $fileSegment, string $visitorClassName): void 63 | { 64 | $visitor = new $visitorClassName($fileSegment); 65 | 66 | $traverser = new NodeTraverser(); 67 | $traverser->addVisitor($visitor); 68 | $traverser->traverse($fileSegment->getStatements()); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Classes/Parser/FileSegments.php: -------------------------------------------------------------------------------- 1 | filePath; 76 | } 77 | 78 | public function setFilePath(string $filePath): void 79 | { 80 | $this->filePath = $filePath; 81 | } 82 | 83 | public function isBaseClass(): bool 84 | { 85 | return $this->baseClass; 86 | } 87 | 88 | public function setBaseClass(bool $baseClass): void 89 | { 90 | $this->baseClass = $baseClass; 91 | } 92 | 93 | public function getCode(): string 94 | { 95 | return $this->code; 96 | } 97 | 98 | public function setCode(string $code): void 99 | { 100 | $this->code = $code; 101 | } 102 | 103 | /** 104 | * @return Node[] 105 | */ 106 | public function getStatements(): array 107 | { 108 | return $this->statements; 109 | } 110 | 111 | /** 112 | * @param Node[] $statements 113 | */ 114 | public function setStatements(array $statements): void 115 | { 116 | $this->statements = $statements; 117 | } 118 | 119 | public function getNamespace(): ?Name 120 | { 121 | return $this->namespace; 122 | } 123 | 124 | public function setNamespace(?Name $namespace): void 125 | { 126 | $this->namespace = $namespace; 127 | } 128 | 129 | /** 130 | * @return UseItem[] 131 | */ 132 | public function getUses(): array 133 | { 134 | return $this->uses; 135 | } 136 | 137 | /** 138 | * @param UseItem[] $uses 139 | */ 140 | public function setUses(array $uses): void 141 | { 142 | $this->uses = $uses; 143 | } 144 | 145 | public function addUse(UseItem $use): void 146 | { 147 | $this->uses[] = $use; 148 | } 149 | 150 | public function getClass(): ?Class_ 151 | { 152 | return $this->class; 153 | } 154 | 155 | public function setClass(?Class_ $class): void 156 | { 157 | $this->class = $class; 158 | } 159 | 160 | /** 161 | * @return TraitUse[] 162 | */ 163 | public function getTraits(): array 164 | { 165 | return $this->traits; 166 | } 167 | 168 | /** 169 | * @param TraitUse[] $traits 170 | */ 171 | public function setTraits(array $traits): void 172 | { 173 | $this->traits = $traits; 174 | } 175 | 176 | public function addTrait(TraitUse $traitUse): void 177 | { 178 | $this->traits[] = $traitUse; 179 | } 180 | 181 | /** 182 | * @return ClassConst[] 183 | */ 184 | public function getClassConsts(): array 185 | { 186 | return $this->classConsts; 187 | } 188 | 189 | /** 190 | * @param ClassConst[] $classConst 191 | */ 192 | public function setClassConsts(array $classConst): void 193 | { 194 | $this->classConsts = $classConst; 195 | } 196 | 197 | public function addClassConst(ClassConst $classConst): void 198 | { 199 | $this->classConsts[] = $classConst; 200 | } 201 | 202 | /** 203 | * @return Property[] 204 | */ 205 | public function getProperties(): array 206 | { 207 | return $this->properties; 208 | } 209 | 210 | /** 211 | * @param Property[] $properties 212 | */ 213 | public function setProperties(array $properties): void 214 | { 215 | $this->properties = $properties; 216 | } 217 | 218 | public function addProperty(Property $property): void 219 | { 220 | $this->properties[] = $property; 221 | } 222 | 223 | public function getConstructor(): ?ClassMethod 224 | { 225 | return $this->constructor; 226 | } 227 | 228 | public function setConstructor(?ClassMethod $constructor): void 229 | { 230 | $this->constructor = $constructor; 231 | } 232 | 233 | public function getInitializeObject(): ?ClassMethod 234 | { 235 | return $this->initializeObject; 236 | } 237 | 238 | public function setInitializeObject(?ClassMethod $initializeObject): void 239 | { 240 | $this->initializeObject = $initializeObject; 241 | } 242 | 243 | /** 244 | * @return ClassMethod[] 245 | */ 246 | public function getMethods(): array 247 | { 248 | return $this->methods; 249 | } 250 | 251 | /** 252 | * @param ClassMethod[] $methods 253 | */ 254 | public function setMethods(array $methods): void 255 | { 256 | $this->methods = $methods; 257 | } 258 | 259 | public function addMethod(ClassMethod $classMethod): void 260 | { 261 | $this->methods[] = $classMethod; 262 | } 263 | 264 | /** 265 | * @return array 266 | */ 267 | public function jsonSerialize(): array 268 | { 269 | return [ 270 | 'filePath' => $this->filePath, 271 | 'baseClass' => $this->baseClass, 272 | 'code' => $this->code, 273 | 'statements' => $this->statements, 274 | 'namespace' => $this->namespace, 275 | 'uses' => $this->uses, 276 | 'class' => $this->class, 277 | 'traits' => $this->traits, 278 | 'properties' => $this->properties, 279 | 'constructor' => $this->constructor, 280 | 'methods' => $this->methods, 281 | ]; 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /Classes/Parser/Visitor/AbstractVisitor.php: -------------------------------------------------------------------------------- 1 | name, $this->disallowedMethodNames) 39 | ) { 40 | $this->fileSegment->addMethod($node); 41 | } 42 | return $node; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Classes/Parser/Visitor/ClassVisitor.php: -------------------------------------------------------------------------------- 1 | stmts = []; 31 | 32 | $this->fileSegment->setClass($class); 33 | } 34 | return $node; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Classes/Parser/Visitor/ConstantVisitor.php: -------------------------------------------------------------------------------- 1 | fileSegment->addClassConst($node); 30 | } 31 | return $node; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Classes/Parser/Visitor/ConstructorVisitor.php: -------------------------------------------------------------------------------- 1 | name === '__construct') { 29 | $this->fileSegment->setConstructor($node); 30 | } 31 | return $node; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Classes/Parser/Visitor/InitializeObjectVisitor.php: -------------------------------------------------------------------------------- 1 | name === 'initializeObject') { 29 | $this->fileSegment->setInitializeObject($node); 30 | } 31 | return $node; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Classes/Parser/Visitor/NamespaceVisitor.php: -------------------------------------------------------------------------------- 1 | fileSegment->setNamespace($node->name); 30 | } 31 | return $node; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Classes/Parser/Visitor/PropertyVisitor.php: -------------------------------------------------------------------------------- 1 | fileSegment->addProperty($node); 30 | } 31 | return $node; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Classes/Parser/Visitor/TraitVisitor.php: -------------------------------------------------------------------------------- 1 | fileSegment->addTrait($node); 30 | } 31 | return $node; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Classes/Parser/Visitor/UseVisitor.php: -------------------------------------------------------------------------------- 1 | fileSegment->addUse($node); 30 | } 31 | return $node; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Configuration/Services.php: -------------------------------------------------------------------------------- 1 | addCompilerPass(new RegisterExtenderPass()); 24 | }; 25 | -------------------------------------------------------------------------------- /Configuration/Services.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | _defaults: 3 | autowire: false 4 | autoconfigure: false 5 | public: false 6 | 7 | Evoweb\Extender\: 8 | resource: '../Classes/' 9 | 10 | cache.extender: 11 | class: TYPO3\CMS\Core\Cache\Frontend\FrontendInterface 12 | # We can not use CacheManager, as it can not be 13 | # injected/instantiated prior (or during) ext_localconf.php 14 | # loading therefore we use an own factory instead. 15 | factory: ['@Evoweb\Extender\Cache\CacheFactory', 'createCache'] 16 | arguments: ['extender'] 17 | 18 | Evoweb\Extender\Cache\ClassCacheManager: 19 | arguments: 20 | $classCache: '@cache.extender' 21 | $classLoader: '@Composer\Autoload\ClassLoader' 22 | $classParser: '@Evoweb\Extender\Parser\ClassParser' 23 | $classComposer: '@Evoweb\Extender\Composer\ClassComposer' 24 | $classRegister: '@Evoweb\Extender\Configuration\ClassRegister' 25 | public: true 26 | 27 | Evoweb\Extender\Command\ClearCommand: 28 | tags: 29 | - name: 'console.command' 30 | command: 'extender:clearClassCache' 31 | description: 'CLI command for the "extender" extension - clear cache' 32 | schedulable: false 33 | arguments: 34 | $cacheFactory: '@Evoweb\Extender\Cache\CacheFactory' 35 | 36 | Evoweb\Extender\Composer\ClassComposer: 37 | public: true 38 | 39 | Evoweb\Extender\Configuration\ClassRegister: 40 | public: true 41 | 42 | Evoweb\Extender\Event\RegisterAutoloaderEvent: 43 | arguments: 44 | $container: '@service_container' 45 | shared: false 46 | 47 | Evoweb\Extender\Hooks\ClearCacheHook: 48 | public: true 49 | arguments: 50 | $classCache: '@cache.extender' 51 | 52 | Evoweb\Extender\Loader\ClassLoader: 53 | arguments: 54 | $classCache: '@cache.extender' 55 | $classCacheManager: '@Evoweb\Extender\Cache\ClassCacheManager' 56 | $classRegister: '@Evoweb\Extender\Configuration\ClassRegister' 57 | public: true 58 | 59 | Evoweb\Extender\Parser\ClassParser: 60 | arguments: 61 | $parserFactory: '@PhpParser\ParserFactory' 62 | public: true 63 | 64 | PhpParser\ParserFactory: 65 | public: true 66 | -------------------------------------------------------------------------------- /Documentation/Breaking/Index.rst: -------------------------------------------------------------------------------- 1 | .. include:: /Includes.rst.txt 2 | .. index:: Breaking changes 3 | .. _breaking-changes: 4 | 5 | =============== 6 | Breaking change 7 | =============== 8 | 9 | Change of command name 10 | ====================== 11 | 12 | The command was renamed from "extender:rebuild" to "extender:clearClassCache" 13 | to better reflect what the command is doing. 14 | 15 | Change of extending configuration in 10.0.0 16 | =========================================== 17 | 18 | Description 19 | ----------- 20 | 21 | Since version 10.0.0 the registration happens in services.yaml 22 | 23 | Impact 24 | ------ 25 | 26 | All class extending in ext_localconf.php needs to be replaced and converted 27 | 28 | Migration 29 | --------- 30 | 31 | Migrate configuration from array to yaml. 32 | 33 | .. code-block:: php 34 | :caption: before ext_localconf.php 35 | 36 | $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['base_extension']['extender'][ 37 | \Fixture\BaseExtension\Domain\Model\Blob::class 38 | ]['extending_extension'] = 'EXT:extending_extension/Classes/Domain/Model/BlobExtend.php'; 39 | 40 | .. code-block:: yaml 41 | :caption: after Services.yaml 42 | 43 | Fixture\ExtendingExtension\Domain\Model\BlobExtend: 44 | tags: 45 | - 46 | name: 'extender.extends' 47 | class: Fixture\BaseExtension\Domain\Model\Blob 48 | 49 | Change of extending configuration in 7.0.0 50 | ========================================== 51 | 52 | Description 53 | ----------- 54 | 55 | Since version 7.0.0 all usage of EXTCONF is replaced with EXTENSIONS. 56 | 57 | Impact 58 | ------ 59 | 60 | All class extending still using EXTCONF to not work anymore. So the code still 61 | fills the array but this array is not used anymore. 62 | 63 | Affected Installations 64 | ---------------------- 65 | 66 | All extensions that use EXTCONF in registration of class extending like. 67 | 68 | .. code-block:: php 69 | :caption: before ext_localconf.php 70 | 71 | $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['store_finder']['extender'][ 72 | \Evoweb\StoreFinder\Domain\Model\Location::class 73 | ]['sitepackage'] = 'EXT:sitepackage/Classes/Domain/Model/Location.php'; 74 | 75 | Migration 76 | --------- 77 | 78 | Replace the usage of EXTCONF with EXTENSIONS to have the class extended again. 79 | -------------------------------------------------------------------------------- /Documentation/Configuration/Index.rst: -------------------------------------------------------------------------------- 1 | .. include:: /Includes.rst.txt 2 | .. index:: Configuration 3 | .. _configuration: 4 | 5 | ============= 6 | Configuration 7 | ============= 8 | 9 | Configure the extend of a class 10 | =============================== 11 | 12 | Since version 10.0.0 the registration of class extends needs to be configured in 13 | services.yaml like in this example. 14 | 15 | .. code-block:: yaml 16 | :caption: Services.yaml 17 | 18 | services: 19 | Fixture\ExtendingExtension\Domain\Model\BlobExtend: 20 | tags: 21 | - 22 | name: 'extender.extends' 23 | class: Fixture\BaseExtension\Domain\Model\Blob 24 | 25 | - Fixture\BaseExtension\Domain\Model\Blob is the class that should be extended 26 | 27 | - Fixture\ExtendingExtension\Domain\Model\BlobExtend is the class that extends 28 | -------------------------------------------------------------------------------- /Documentation/Includes.rst.txt: -------------------------------------------------------------------------------- 1 | .. You can put central messages to display on all pages here 2 | -------------------------------------------------------------------------------- /Documentation/Index.rst: -------------------------------------------------------------------------------- 1 | .. include:: /Includes.rst.txt 2 | 3 | .. _start: 4 | 5 | ============================================= 6 | Extender: Extbase class extending on steroids 7 | ============================================= 8 | 9 | :Extension key: 10 | extender 11 | 12 | :Package name: 13 | evoweb/extender 14 | 15 | :Version: 16 | |release| 17 | 18 | :Language: 19 | en 20 | 21 | :Author: 22 | Sebastian Fischer 23 | 24 | :License: 25 | This document is published under the 26 | `Open Publication `__. 27 | license. 28 | 29 | :Rendered: 30 | |today| 31 | 32 | :Copyright: 33 | 2014-2023 34 | 35 | ---- 36 | 37 | Makes extending classes fully transparent 38 | 39 | The content of this document is related to TYPO3, 40 | a GNU/GPL CMS/Framework available from http://typo3.org 41 | 42 | ---- 43 | 44 | **Table of Contents** 45 | 46 | .. toctree:: 47 | :maxdepth: 2 48 | :titlesonly: 49 | 50 | Introduction/Index 51 | Installation/Index 52 | Configuration/Index 53 | Breaking/Index 54 | Testing/Index 55 | 56 | .. Meta Menu 57 | 58 | .. toctree:: 59 | :hidden: 60 | 61 | Sitemap 62 | -------------------------------------------------------------------------------- /Documentation/Installation/Index.rst: -------------------------------------------------------------------------------- 1 | .. include:: /Includes.rst.txt 2 | .. index:: Installation 3 | .. _installation: 4 | 5 | ============ 6 | Installation 7 | ============ 8 | 9 | As EXT:extender is based on composer and its mechanisms the installation is only 10 | possible with composer methods. 11 | 12 | **WARNING** In previous versions it was possible to use extender when installed 13 | via the Extension Manager. This is not possible anymore. 14 | 15 | Require via command 16 | =================== 17 | 18 | You can add extender with composer require. 19 | 20 | .. code-block:: bash 21 | composer require evoweb/extender 22 | 23 | Modify composer.json 24 | ==================== 25 | 26 | Additionally evoweb/extender can be added to the require in your composer.json, 27 | like in the following example and run 'composer install'. 28 | 29 | .. code-block:: json 30 | :caption: composer.json 31 | 32 | { 33 | "require": { 34 | ... 35 | "evoweb/extender": "*" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Documentation/Introduction/Images/base.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evoWeb/extender/279bf9bbc00a2c5ea300a14a8d5445cf006e269d/Documentation/Introduction/Images/base.png -------------------------------------------------------------------------------- /Documentation/Introduction/Images/extend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evoWeb/extender/279bf9bbc00a2c5ea300a14a8d5445cf006e269d/Documentation/Introduction/Images/extend.png -------------------------------------------------------------------------------- /Documentation/Introduction/Images/merged.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evoWeb/extender/279bf9bbc00a2c5ea300a14a8d5445cf006e269d/Documentation/Introduction/Images/merged.png -------------------------------------------------------------------------------- /Documentation/Introduction/Index.rst: -------------------------------------------------------------------------------- 1 | .. include:: /Includes.rst.txt 2 | .. index:: Introduction 3 | .. _introduction: 4 | 5 | ============ 6 | Introduction 7 | ============ 8 | 9 | This Documentation was written for version 7.0.x of the extension. 10 | 11 | Extending classes 12 | ================= 13 | 14 | The sole purpose of this extension is to fill a gap that extbase leaves open. 15 | 16 | As it is not possible to extend classes in extbase directly its sometimes not 17 | possible to add properties and methods to a class. This is valid for all cases 18 | where the class is given as an type hint to an action. 19 | 20 | Arguments of an action are mapped from the request to the concrete class by 21 | the argument mapper. The argument mapper does not take TypoScript object 22 | className mapping into account as the property mapper. Due to this its not 23 | possible to just extend the class and have the extends available. 24 | 25 | To cope with this and be able to add custom properties to a class the extender 26 | registers a custom spl class loader. This kicks in for every configured class 27 | and required a compiled class from the class cache. 28 | 29 | The class cache gets generated on every hit where an configured compilation is 30 | not available and after every clear system cache. In both cases the class cache 31 | manager rebuilds the complete class cache. 32 | 33 | As the class cache is registered to the system cache group it gets cleared on 34 | every clear system cache or clear all caches. After that a hook gets called that 35 | rebuilds the class cache. Unless a huge amount of extends are configured there 36 | should be a prefilled class cache on every request. 37 | 38 | Deep dive into an example 39 | ========================= 40 | 41 | The cache manager parses the base and extend files to gather the colored parts 42 | of the following images, generates a combined file as shown in the merged result 43 | and adds it to the cache. 44 | 45 | .. rst-class:: bignums 46 | 47 | - Base class file 48 | 49 | .. figure:: Images/base.png 50 | 51 | - Extend class file 52 | 53 | .. figure:: Images/extend.png 54 | 55 | - Merged result file 56 | 57 | .. figure:: Images/merged.png 58 | 59 | Explanation 60 | =========== 61 | 62 | .. rst-class:: bignums-attention 63 | 64 | - Namespace 65 | 66 | The Namespace is taken from the base file. The namespace of the extended 67 | file is ignored. 68 | 69 | - Uses 70 | 71 | All uses from base and extending file are taken uniquely. If an use appears 72 | with diverting as alias it is present twice in the merged file. 73 | 74 | .. code-block:: 75 | :caption: example of uses appearing twice 76 | 77 | use Psr\Log\LoggerAwareTrait; 78 | use Psr\Log\LoggerAwareTrait as T; 79 | 80 | - Class 81 | 82 | The class name and the extends part is taken from the base class. 83 | 84 | - Implements 85 | 86 | Implements are used uniquely from the base and extend file 87 | 88 | - Traits 89 | 90 | All traits from base and extend file are taken uniquely. 91 | 92 | - Properties 93 | 94 | All properties from base and extend file are taken without check if they 95 | are not colliding. 96 | 97 | - Construct 98 | 99 | The __construct of base and extend file are taken with merged contents and 100 | arguments. Where arguments from base take priority. 101 | All line of code in the method are taken. If the __construct of the extend 102 | file contains a parent::__construct call it gets removed. 103 | 104 | - Methods 105 | 106 | All methods beside __construct from base and extend file are taken without 107 | check if they are not colliding. 108 | 109 | - Comment 110 | 111 | The comment is based on the base and extending files and display which 112 | files path were taken into account. 113 | 114 | Example source 115 | ============== 116 | 117 | The base file content could be found in 118 | EXT:extender/Tests/Fixtures/Extensions/base_extension/Classes/Domain/Model/Blob.php 119 | 120 | The extend file content is derived from 121 | EXT:extender/Tests/Fixtures/Extensions/extending_extension/Classes/Domain/Model/BlobExtend.php 122 | 123 | Important 124 | ========= 125 | 126 | Correct 127 | ----- 128 | 129 | As in both files shown, it's important to use the FQCN to extend of, else the 130 | usage of the class gets written to the merged file and result in two classes 131 | with the same name in the cache file. 132 | 133 | .. code-block:: php 134 | :caption: Correct extending 135 | 136 | namespace Fixture\ExtendingClass\Domain\Model; 137 | 138 | class ExtendingModel extends \Fixture\BaseClass\Domain\Model\BaseModel 139 | { 140 | } 141 | 142 | .. code-block:: php 143 | :caption: Result with correct extending 144 | 145 | namespace Fixture\ExtendingClass\Domain\Model; 146 | 147 | class BaseModel 148 | { 149 | } 150 | 151 | Wrong 152 | ----- 153 | 154 | While linting the file will not raise an error and the class is usable, 155 | it will definitely irritate editors like PHPStorm or Visual Code. 156 | 157 | .. code-block:: php 158 | :caption: Wrong extension 159 | 160 | namespace Fixture\ExtendingClass\Domain\Model; 161 | 162 | use Fixture\BaseClass\Domain\Model\BaseModel; 163 | 164 | class ExtendingModel extends BaseModel 165 | { 166 | } 167 | 168 | .. code-block:: php 169 | :caption: Result with wrong extension 170 | 171 | namespace Fixture\BaseClass\Domain\Model; 172 | 173 | use Fixture\BaseClass\Domain\Model\BaseModel; 174 | 175 | class BaseModel 176 | { 177 | } 178 | -------------------------------------------------------------------------------- /Documentation/Sitemap.rst: -------------------------------------------------------------------------------- 1 | :template: sitemap.html 2 | 3 | .. include:: /Includes.rst.txt 4 | 5 | ======= 6 | Sitemap 7 | ======= 8 | 9 | .. The sitemap.html template will insert here the page tree automatically. 10 | -------------------------------------------------------------------------------- /Documentation/Testing/Images/phpstorm_configuration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evoWeb/extender/279bf9bbc00a2c5ea300a14a8d5445cf006e269d/Documentation/Testing/Images/phpstorm_configuration.png -------------------------------------------------------------------------------- /Documentation/Testing/Index.rst: -------------------------------------------------------------------------------- 1 | .. include:: /Includes.rst.txt 2 | .. index:: Testing 3 | .. _testing: 4 | 5 | ======= 6 | Testing 7 | ======= 8 | 9 | .. figure:: Images/phpstorm_configuration.png 10 | :alt: Setting up configuration for Functional Testing in PhpStorm 11 | -------------------------------------------------------------------------------- /Documentation/guides.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 17 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, see . 308 | 309 | Also add information on how to contact you by electronic and paper mail. 310 | 311 | If the program is interactive, make it output a short notice like this 312 | when it starts in an interactive mode: 313 | 314 | Gnomovision version 69, Copyright (C) year name of author 315 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 316 | This is free software, and you are welcome to redistribute it 317 | under certain conditions; type `show c' for details. 318 | 319 | The hypothetical commands `show w' and `show c' should show the appropriate 320 | parts of the General Public License. Of course, the commands you use may 321 | be called something other than `show w' and `show c'; they could even be 322 | mouse-clicks or menu items--whatever suits your program. 323 | 324 | You should also get your employer (if you work as a programmer) or your 325 | school, if any, to sign a "copyright disclaimer" for the program, if 326 | necessary. Here is a sample; alter the names: 327 | 328 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 329 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 330 | 331 | , 1 April 1989 332 | Moe Ghoul, President of Vice 333 | 334 | This General Public License does not permit incorporating your program into 335 | proprietary programs. If your program is a subroutine library, you may 336 | consider it more useful to permit linking proprietary applications with the 337 | library. If this is what you want to do, use the GNU Lesser General 338 | Public License instead of this License. 339 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TYPO3 Extending extbase domain models 2 | 3 | ![build](https://github.com/evoWeb/extender/actions/workflows/ci.yml/badge.svg?branch=develop) 4 | [![Latest Stable Version](https://poser.pugx.org/evoweb/extender/v/stable)](https://packagist.org/packages/evoweb/extender) 5 | [![Monthly Downloads](https://poser.pugx.org/evoweb/extender/d/monthly)](https://packagist.org/packages/evoweb/extender) 6 | [![Total Downloads](https://poser.pugx.org/evoweb/extender/downloads)](https://packagist.org/packages/evoweb/extender) 7 | 8 | ## Installation 9 | 10 | ### via Composer 11 | 12 | The recommended way to install EXT:extender is by using [Composer](https://getcomposer.org): 13 | 14 | composer require evoweb/extender 15 | 16 | ### quick introduction 17 | 18 | Add the extending classname to your packages Services.yaml and add a tag to it. 19 | The tag must contain the name 'extender.extends' and the class it is extending. 20 | 21 | Services.yaml 22 | ```yaml 23 | services: 24 | 25 | Fixture\ExtendingExtension\Domain\Model\BlobExtend: 26 | tags: 27 | - 28 | name: 'extender.extends' 29 | class: Fixture\BaseExtension\Domain\Model\Blob 30 | ``` 31 | -------------------------------------------------------------------------------- /Resources/Public/Icons/Extension.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "evoweb/extender", 3 | "type": "typo3-cms-extension", 4 | "description": "Extending extbase domain models like a pro with extender", 5 | "homepage": "https://www.evoweb.de", 6 | "license": [ 7 | "GPL-2.0-or-later" 8 | ], 9 | "keywords": [ 10 | "TYPO3", 11 | "extbase", 12 | "extending", 13 | "domain models" 14 | ], 15 | "support" : { 16 | "source" : "https://github.com/evoWeb/extender", 17 | "issues" : "https://github.com/evoWeb/extender/issues", 18 | "docs": "https://docs.typo3.org/p/evoweb/extender/main/en-us/" 19 | }, 20 | "config": { 21 | "vendor-dir": "Build/vendor", 22 | "bin-dir": "bin", 23 | "allow-plugins": { 24 | "typo3/class-alias-loader": true, 25 | "typo3/cms-composer-installers": true 26 | } 27 | }, 28 | "require": { 29 | "typo3/cms-core": "^13.0 || 13.3.x-dev || dev-main", 30 | "nikic/php-parser": "^5.3", 31 | "psr/container": "^2.0", 32 | "psr/event-dispatcher": "^1.0", 33 | "symfony/console": "^7.0", 34 | "symfony/dependency-injection": "^7.0" 35 | }, 36 | "require-dev": { 37 | "friendsofphp/php-cs-fixer": "^3.64.0", 38 | "friendsoftypo3/phpstan-typo3": "^0.9.0", 39 | "phpstan/phpdoc-parser": "^1.30.0", 40 | "phpstan/phpstan": "^1.12.5", 41 | "phpunit/phpunit": "^11.0.3", 42 | "typo3/testing-framework": "dev-main", 43 | "evowebtests/base-extension": "*", 44 | "evowebtests/extending-extension": "*", 45 | "typo3/cms-extensionmanager": "^13.0 || 13.0.x-dev || dev-main" 46 | }, 47 | "minimum-stability": "dev", 48 | "prefer-stable": true, 49 | "extra": { 50 | "typo3/cms": { 51 | "extension-key": "extender", 52 | "app-dir": "Build", 53 | "web-dir": "Build/Web" 54 | } 55 | }, 56 | "scripts": { 57 | "prepare-release": [ 58 | "rm -rf .github", 59 | "rm -rf Build", 60 | "rm -rf Tests", 61 | "rm .gitattributes", 62 | "rm .gitignore", 63 | "sed -i \"s/version' => '.*'/version' => '$(echo ${GITHUB_REF} | cut -d / -f 3)'/\" ext_emconf.php\n" 64 | ], 65 | "post-install-cmd": [ 66 | "ln -sf vendor/typo3/testing-framework/Resources/Core/Build/ Build/phpunit;" 67 | ], 68 | "post-update-cmd": [ 69 | "@post-install-cmd" 70 | ], 71 | "post-autoload-dump": [ 72 | "TYPO3\\TestingFramework\\Composer\\ExtensionTestEnvironment::prepare" 73 | ] 74 | }, 75 | "autoload": { 76 | "psr-4": { 77 | "Evoweb\\Extender\\": "Classes/" 78 | } 79 | }, 80 | "autoload-dev": { 81 | "psr-4": { 82 | "Evoweb\\Extender\\Tests\\": "Tests/" 83 | } 84 | }, 85 | "repositories": { 86 | "files": { 87 | "type": "path", 88 | "url": "./Tests/Fixtures/Extensions/*" 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /ext_emconf.php: -------------------------------------------------------------------------------- 1 | 'Extbase Domain Model Extender', 5 | 'description' => 'A services that enables adding properties and functions 6 | to classes by implementing the proxy pattern', 7 | 'category' => 'misc', 8 | 'author' => 'Sebastian Fischer', 9 | 'author_email' => 'extender@evoweb.de', 10 | 'author_company' => 'evoWeb', 11 | 'state' => 'stable', 12 | 'version' => '11.0.2', 13 | 'constraints' => [ 14 | 'depends' => [ 15 | 'typo3' => '13.0.0-13.4.99', 16 | ], 17 | ], 18 | ]; 19 | -------------------------------------------------------------------------------- /ext_localconf.php: -------------------------------------------------------------------------------- 1 |