Skip to content

Instantly share code, notes, and snippets.

@AydinHassan
Created August 11, 2016 11:11
Show Gist options
  • Save AydinHassan/064f4bbd33fc118f9fa655811df6a660 to your computer and use it in GitHub Desktop.
Save AydinHassan/064f4bbd33fc118f9fa655811df6a660 to your computer and use it in GitHub Desktop.
Magento 2 Interceptor with return type hints patch
From d5951cd822ae615c8ae56e1c92aae0a921ad611c Mon Sep 17 00:00:00 2001
From: Aydin Hassan <aydin@hotmail.co.uk>
Date: Thu, 11 Aug 2016 13:00:42 +0200
Subject: [PATCH] Patch interceptor generator to add return types, bring in
some zend-code 3 features
---
Generation/ClassGenerator.php | 37 +++++++++++++
Generation/Interceptor.php | 42 +++++++++++++++
Generation/MethodGenerator.php | 112 ++++++++++++++++++++++++++++++++++++++
Generation/TypeGenerator.php | 118 +++++++++++++++++++++++++++++++++++++++++
etc/di.xml | 7 +++
5 files changed, 316 insertions(+)
create mode 100644 Generation/ClassGenerator.php
create mode 100644 Generation/Interceptor.php
create mode 100644 Generation/MethodGenerator.php
create mode 100644 Generation/TypeGenerator.php
diff --git a/Generation/ClassGenerator.php b/Generation/ClassGenerator.php
new file mode 100644
index 0000000..dd978cf
--- /dev/null
+++ b/Generation/ClassGenerator.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace Jh\Toolkit\Generation;
+
+use Magento\Framework\Code\Generator\ClassGenerator as MagentoClassGenerator;
+
+/**
+ * @author Aydin Hassan <aydin@hotmail.co.uk>
+ */
+class ClassGenerator extends MagentoClassGenerator
+{
+ /**
+ * Possible class method options
+ *
+ * @var array
+ */
+ protected $_methodOptions = [
+ 'name' => 'setName',
+ 'final' => 'setFinal',
+ 'static' => 'setStatic',
+ 'abstract' => 'setAbstract',
+ 'visibility' => 'setVisibility',
+ 'body' => 'setBody',
+ 'returnType' => 'setReturnType'
+ ];
+
+
+ /**
+ * Instantiate method generator object.
+ *
+ * @return MethodGenerator
+ */
+ protected function createMethodGenerator()
+ {
+ return new MethodGenerator();
+ }
+}
diff --git a/Generation/Interceptor.php b/Generation/Interceptor.php
new file mode 100644
index 0000000..bb3c214
--- /dev/null
+++ b/Generation/Interceptor.php
@@ -0,0 +1,42 @@
+<?php
+
+namespace Jh\Toolkit\Generation;
+
+use Magento\Framework\Interception\Code\Generator\Interceptor as MagentoInterceptor;
+
+/**
+ * @author Aydin Hassan <aydin@hotmail.co.uk>
+ */
+class Interceptor extends MagentoInterceptor
+{
+ /**
+ * Retrieve method info
+ *
+ * @param \ReflectionMethod $method
+ * @return array
+ */
+ protected function _getMethodInfo(\ReflectionMethod $method)
+ {
+ $parameters = [];
+ foreach ($method->getParameters() as $parameter) {
+ $parameters[] = $this->_getMethodParameterInfo($parameter);
+ }
+
+ $methodInfo = [
+ 'name' => $method->getName(),
+ 'parameters' => $parameters,
+ 'body' => "\$pluginInfo = \$this->pluginList->getNext(\$this->subjectType, '{$method->getName()}');\n" .
+ "if (!\$pluginInfo) {\n" .
+ " return parent::{$method->getName()}({$this->_getParameterList(
+ $parameters
+ )});\n" .
+ "} else {\n" .
+ " return \$this->___callPlugins('{$method->getName()}', func_get_args(), \$pluginInfo);\n" .
+ "}",
+ 'docblock' => ['shortDescription' => '{@inheritdoc}'],
+ 'returnType' => $method->getReturnType()
+ ];
+
+ return $methodInfo;
+ }
+}
diff --git a/Generation/MethodGenerator.php b/Generation/MethodGenerator.php
new file mode 100644
index 0000000..0fa61ce
--- /dev/null
+++ b/Generation/MethodGenerator.php
@@ -0,0 +1,112 @@
+<?php
+
+namespace Jh\Toolkit\Generation;
+
+use Zend\Code\Generator\MethodGenerator as ZendMethodGenerator;
+
+/**
+ * @author Aydin Hassan <aydin@hotmail.co.uk>
+ */
+class MethodGenerator extends ZendMethodGenerator
+{
+ /**
+ * @var null|TypeGenerator
+ */
+ private $returnType;
+
+ /**
+ * @var bool
+ */
+ private $returnsReference = false;
+
+ /**
+ * @param bool $returnsReference
+ *
+ * @return MethodGenerator
+ */
+ public function setReturnsReference($returnsReference)
+ {
+ $this->returnsReference = (bool) $returnsReference;
+ return $this;
+ }
+
+ /**
+ * @param string|null
+ *
+ * @return MethodGenerator
+ */
+ public function setReturnType($returnType = null)
+ {
+ $this->returnType = null === $returnType
+ ? null
+ : TypeGenerator::fromTypeString($returnType);
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ /**
+ * @return string
+ */
+ public function generate()
+ {
+ $output = '';
+
+ $indent = $this->getIndentation();
+
+ if (($docBlock = $this->getDocBlock()) !== null) {
+ $docBlock->setIndentation($indent);
+ $output .= $docBlock->generate();
+ }
+
+ $output .= $indent;
+
+ if ($this->isAbstract()) {
+ $output .= 'abstract ';
+ } else {
+ $output .= (($this->isFinal()) ? 'final ' : '');
+ }
+
+ $output .= $this->getVisibility()
+ . (($this->isStatic()) ? ' static' : '')
+ . ' function '
+ . ($this->returnsReference ? '& ' : '')
+ . $this->getName() . '(';
+
+ $parameters = $this->getParameters();
+ if (!empty($parameters)) {
+ foreach ($parameters as $parameter) {
+ $parameterOutput[] = $parameter->generate();
+ }
+
+ $output .= implode(', ', $parameterOutput);
+ }
+
+ $output .= ')';
+
+ if ($this->returnType) {
+ $output .= ' : ' . $this->returnType->generate();
+ }
+
+ if ($this->isAbstract()) {
+ return $output . ';';
+ }
+
+// not supporting interfaces
+// if ($this->isInterface()) {
+// return $output . ';';
+// }
+
+ $output .= self::LINE_FEED . $indent . '{' . self::LINE_FEED;
+
+ if ($this->body) {
+ $output .= preg_replace('#^((?![a-zA-Z0-9_-]+;).+?)$#m', $indent . $indent . '$1', trim($this->body))
+ . self::LINE_FEED;
+ }
+
+ $output .= $indent . '}' . self::LINE_FEED;
+
+ return $output;
+ }
+}
diff --git a/Generation/TypeGenerator.php b/Generation/TypeGenerator.php
new file mode 100644
index 0000000..3cf736b
--- /dev/null
+++ b/Generation/TypeGenerator.php
@@ -0,0 +1,118 @@
+<?php
+
+namespace Jh\Toolkit\Generation;
+
+use Zend\Code\Generator\Exception\InvalidArgumentException;
+use Zend\Code\Generator\GeneratorInterface;
+
+final class TypeGenerator implements GeneratorInterface
+{
+ /**
+ * @var bool
+ */
+ private $isInternalPhpType;
+
+ /**
+ * @var string
+ */
+ private $type;
+
+ /**
+ * @var string[]
+ *
+ * @link http://php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration
+ */
+ private static $internalPhpTypes = ['int', 'float', 'string', 'bool', 'array', 'callable'];
+
+ // @codingStandardsIgnoreStart
+ /**
+ * @var string a regex pattern to match valid class names or types
+ */
+ private static $validIdentifierMatcher = '/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(\\\\[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*$/';
+ // @codingStandardsIgnoreEnd
+
+ /**
+ * @param string $type
+ *
+ * @return TypeGenerator
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function fromTypeString($type)
+ {
+ list($wasTrimmed, $trimmedType) = self::trimType($type);
+
+ if (! preg_match(self::$validIdentifierMatcher, $trimmedType)) {
+ throw new InvalidArgumentException(sprintf(
+ 'Provided type "%s" is invalid: must conform "%s"',
+ $type,
+ self::$validIdentifierMatcher
+ ));
+ }
+
+ $isInternalPhpType = self::isInternalPhpType($trimmedType);
+
+ if ($wasTrimmed && $isInternalPhpType) {
+ throw new InvalidArgumentException(sprintf(
+ 'Provided type "%s" is an internal PHP type, but was provided with a namespace separator prefix',
+ $type
+ ));
+ }
+
+ $instance = new self();
+
+ $instance->type = $trimmedType;
+ $instance->isInternalPhpType = self::isInternalPhpType($trimmedType);
+
+ return $instance;
+ }
+
+ private function __construct()
+ {
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function generate()
+ {
+ if ($this->isInternalPhpType) {
+ return strtolower($this->type);
+ }
+
+ return '\\' . $this->type;
+ }
+
+ /**
+ * @return string the cleaned type string
+ */
+ public function __toString()
+ {
+ return ltrim($this->generate(), '\\');
+ }
+
+ /**
+ * @param string $type
+ *
+ * @return bool[]|int[] ordered tuple, first key represents whether the values was trimmed, second is the
+ * trimmed string
+ */
+ private static function trimType($type)
+ {
+ if (0 === strpos($type, '\\')) {
+ return [true, substr($type, 1)];
+ }
+
+ return [false, $type];
+ }
+
+ /**
+ * @param string $type
+ *
+ * @return bool
+ */
+ private static function isInternalPhpType($type)
+ {
+ return in_array(strtolower($type), self::$internalPhpTypes, true);
+ }
+}
\ No newline at end of file
diff --git a/etc/di.xml b/etc/di.xml
index e98b83d..6675a45 100644
--- a/etc/di.xml
+++ b/etc/di.xml
@@ -7,4 +7,11 @@
</argument>
</arguments>
</type>
+ <type name="Magento\Framework\Interception\Code\Generator\Interceptor">
+ <arguments>
+ <argument name="classGenerator" xsi:type="object" shared="false">Jh\Toolkit\Generation\ClassGenerator</argument>
+ </arguments>
+ </type>
+ <preference for="\Magento\Framework\Interception\Code\Generator\Interceptor"
+ type="Jh\Toolkit\Generation\Interceptor" />
</config>
\ No newline at end of file
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment