Last active
July 19, 2021 13:58
-
-
Save mattwellss/5b133af5cf69793d0953d5fa0087e6ea to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| // phpcs:ignoreFile | |
| use bitExpert\PHPStan\Magento\Autoload\Cache\FileCacheStorage; | |
| use Magento\Framework\App\DeploymentConfig; | |
| use Magento\Framework\App\DeploymentConfig\Reader as DeploymentConfigReader; | |
| use Magento\Framework\App\Filesystem\DirectoryList; | |
| use Magento\Framework\Component\ComponentRegistrar; | |
| use Magento\Framework\Config\File\ConfigFilePool; | |
| use Magento\Framework\Filesystem; | |
| use Magento\Framework\Filesystem\Driver\File as FileDriver; | |
| use Magento\Framework\Module\Declaration\Converter\Dom as ModuleDeclarationDom; | |
| use Magento\Framework\Module\ModuleList; | |
| use Magento\Framework\Module\ModuleList\Loader; | |
| use Magento\Framework\Xml\Parser as XmlParser; | |
| use Nette\Neon\Neon; | |
| (function (array $argv) { | |
| require_once __DIR__ . '/ExtensionInterfaceAutoloader.php'; | |
| $configFile = ''; | |
| if (count($argv) > 0) { | |
| foreach ($argv as $idx => $value) { | |
| if ((strtolower($value) === '-c') && isset($argv[$idx + 1])) { | |
| $configFile = $argv[$idx + 1]; | |
| break; | |
| } | |
| } | |
| } | |
| if (empty($configFile)) { | |
| $currentWorkingDirectory = getcwd(); | |
| foreach (['phpstan.neon', 'phpstan.neon.dist'] as $discoverableConfigName) { | |
| $discoverableConfigFile = $currentWorkingDirectory . DIRECTORY_SEPARATOR . $discoverableConfigName; | |
| if (file_exists($discoverableConfigFile) && is_readable(($discoverableConfigFile))) { | |
| $configFile = $discoverableConfigFile; | |
| break; | |
| } | |
| } | |
| } | |
| $tmpDir = sys_get_temp_dir() . '/phpstan'; | |
| if (!empty($configFile)) { | |
| $neonConfig = Neon::decode(file_get_contents($configFile)); | |
| if (is_array($neonConfig) && isset($neonConfig['parameters']) && isset($neonConfig['parameters']['tmpDir'])) { | |
| $tmpDir = $neonConfig['parameters']['tmpDir']; | |
| } | |
| } | |
| $componentRegistrar = new ComponentRegistrar(); | |
| $loader = new \Lfi\Codetools\PHPStan\ExtensionInterfaceAutoloader( | |
| new ModuleList( | |
| new DeploymentConfig( | |
| new DeploymentConfigReader( | |
| new DirectoryList('.'), | |
| new Filesystem\DriverPool(), | |
| new ConfigFilePool() | |
| ) | |
| ), | |
| new Loader( | |
| new ModuleDeclarationDom(), | |
| new XmlParser(), | |
| $componentRegistrar, | |
| new FileDriver() | |
| ) | |
| ), | |
| $componentRegistrar, | |
| new \PHPStan\Cache\Cache(new FileCacheStorage($tmpDir . '/cache/PHPStan')) | |
| ); | |
| \spl_autoload_register([$loader, 'autoload'], true, false); | |
| })($GLOBALS['argv'] ?? []); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| namespace Lfi\Codetools\PHPStan; | |
| use Laminas\Code\Generator\DocBlock\Tag\ParamTag; | |
| use Laminas\Code\Generator\DocBlock\Tag\ReturnTag; | |
| use Laminas\Code\Generator\DocBlockGenerator; | |
| use Laminas\Code\Generator\InterfaceGenerator; | |
| use Laminas\Code\Generator\MethodGenerator; | |
| use Magento\Framework\Api\SimpleDataObjectConverter; | |
| use Magento\Framework\Component\ComponentRegistrar; | |
| use Magento\Framework\Module\ModuleList; | |
| use Symfony\Component\Finder\Finder; | |
| class ExtensionInterfaceAutoloader | |
| { | |
| private $moduleList; | |
| private $componentRegistrar; | |
| private $cache; | |
| private $xmlDocs; | |
| public function __construct( | |
| ModuleList $moduleList, | |
| ComponentRegistrar $componentRegistrar, | |
| \PHPStan\Cache\Cache $cache | |
| ) { | |
| $this->moduleList = $moduleList; | |
| $this->componentRegistrar = $componentRegistrar; | |
| $this->cache = $cache; | |
| } | |
| public function autoload(string $class): void | |
| { | |
| if (preg_match('/ExtensionInterface$/', $class) !== 1) { | |
| return; | |
| } | |
| $cachedFilename = $this->cache->load($class, ''); | |
| if (!$cachedFilename) { | |
| try { | |
| $this->cache->save($class, '', $this->getFileContents($class)); | |
| $cachedFilename = $this->cache->load($class, ''); | |
| } catch (\Exception $e) { | |
| return; | |
| } | |
| } | |
| // phpcs:ignore Magento2.Security.IncludeFile.FoundIncludeFile | |
| require_once $cachedFilename; | |
| } | |
| /** | |
| * Given an extension attributes interface name, generate that interface (if possible) | |
| */ | |
| public function getFileContents(string $interfaceName): string | |
| { | |
| /** | |
| * Given a classname to autoload (such as Magento\Catalog\Api\Data\ProductExtensionInterface), | |
| * generate the entity's interface name (like Magento\Catalog\Api\Data\ProductInterface) | |
| * @see \Magento\Framework\Code\Generator::generateClass | |
| * @see \Magento\Framework\Api\Code\Generator\ExtensionAttributesGenerator::__construct | |
| */ | |
| $sourceInterface = rtrim(substr($interfaceName, 0, -1 * strlen('ExtensionInterface')), '\\') . 'Interface'; | |
| // Magento only creates extension attribute interfaces for existing interfaces; retain that logic | |
| if (!interface_exists($sourceInterface)) { | |
| throw new \Exception("${sourceInterface} does not exist and has no extension interface"); | |
| } | |
| $generator = new InterfaceGenerator(); | |
| $generator | |
| ->setName($interfaceName) | |
| ->setImplementedInterfaces([\Magento\Framework\Api\ExtensionAttributesInterface::class]); | |
| foreach ($this->getExtensionAttributesXmlDocs() as $doc) { | |
| $xpath = new \DOMXPath($doc); | |
| $attrs = $xpath->query( | |
| "//extension_attributes[@for=\"${sourceInterface}\"]/attribute", | |
| $doc->documentElement | |
| ); | |
| /** @var \DOMElement $attr */ | |
| foreach ($attrs as $attr) { | |
| /** | |
| * Generate getters and setters for each extension attribute | |
| * @see \Magento\Framework\Api\Code\Generator\ExtensionAttributesGenerator::_getClassMethods | |
| */ | |
| $propertyName = SimpleDataObjectConverter::snakeCaseToCamelCase($attr->getAttribute('code')); | |
| $type = $attr->getAttribute('type'); | |
| $generator->addMethodFromGenerator( | |
| MethodGenerator::fromArray([ | |
| 'name' => 'get' . ucfirst($propertyName), | |
| 'docblock' => DocBlockGenerator::fromArray([ | |
| 'tags' => [ | |
| new ReturnTag([$type, 'null']), | |
| ], | |
| ]), | |
| ]) | |
| ); | |
| $generator->addMethodFromGenerator( | |
| MethodGenerator::fromArray([ | |
| 'name' => 'set' . ucfirst($propertyName), | |
| 'parameters' => [$propertyName], | |
| 'docblock' => DocBlockGenerator::fromArray([ | |
| 'tags' => [ | |
| new ParamTag( | |
| $propertyName, | |
| [ | |
| $type, | |
| 'null' | |
| ] | |
| ), | |
| new ReturnTag( | |
| '$this' | |
| ) | |
| ] | |
| ]) | |
| ]) | |
| ); | |
| } | |
| } | |
| return "<?php\n\n" . $generator->generate(); | |
| } | |
| /** | |
| * Create a generator which creates DOM documents for every extension attributes XML file in enabled modules | |
| * @return \DOMDocument[] | |
| */ | |
| private function getExtensionAttributesXmlDocs(): array | |
| { | |
| if (is_array($this->xmlDocs)) { | |
| return $this->xmlDocs; | |
| } | |
| $enabledModuleDirs = array_filter( | |
| $this->componentRegistrar->getPaths(ComponentRegistrar::MODULE), | |
| function ($moduleName) { | |
| return $this->moduleList->has($moduleName); | |
| }, | |
| ARRAY_FILTER_USE_KEY | |
| ); | |
| $finder = Finder::create() | |
| ->files() | |
| ->in(array_map(function ($dir) { | |
| return $dir . '/etc'; | |
| }, $enabledModuleDirs)) | |
| ->name('extension_attributes.xml'); | |
| $this->xmlDocs = []; | |
| foreach ($finder as $item) { | |
| $doc = new \DOMDocument(); | |
| $doc->loadXML($item->getContents()); | |
| $this->xmlDocs[] = $doc; | |
| } | |
| return $this->xmlDocs; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment