Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Incorrect default value for methods in \Psr\Log\LoggerInterface #78

Closed
ihor-sviziev opened this issue Nov 4, 2020 · 7 comments
Closed

Comments

@ihor-sviziev
Copy link

ihor-sviziev commented Nov 4, 2020

Originally issue came from the Magento 2: magento/magento2#30575

We faced an issue with auto-generated proxy code based on the \Psr\Log\LoggerInterface.
Investigation shown that actually issue was caused by psr php extension - seems like default value isn't specified, while on original LoggerInterface we do have optional context argument wit default value - empty array.

Tested on php 7.3 and php 7.4, both versions having the same issue.

I was able to reproduce this issue even on a simple file (checked on emergency method, but issue the same for others):

<?php
include "./vendor/autoload.php";

$reflectionMethod = new \ReflectionMethod(\Psr\Log\LoggerInterface::class, "emergency");
$reflectionParameter = $reflectionMethod->getParameters()[1];
var_dump($reflectionParameter->isOptional());
var_dump($reflectionParameter->isDefaultValueAvailable());
var_dump($reflectionParameter->getDefaultValue());

and psr/log installed through composer.

Expected result

Result without psr extension

app@3bedbfa9e5db:~/html/pub$ php index.php 
bool(true)
bool(true)
array(0) {
}

Actual result

Result with psr extension

app@3bedbfa9e5db:~/html/pub$ php index.php 
bool(true)
bool(false)

Fatal error: Uncaught ReflectionException: Cannot determine default value for internal functions in /var/www/html/pub/index.php:8
Stack trace:
#0 /var/www/html/pub/index.php(8): ReflectionParameter->getDefaultValue()
#1 {main}
  thrown in /var/www/html/pub/index.php on line 8
@ihor-sviziev
Copy link
Author

@jbboehr could you check this issue?

@jbboehr
Copy link
Owner

jbboehr commented Nov 5, 2020

This does appear to be an issue, however the API for describing default parameter values in PHP 7 for extensions doesn't seem to allow it:
https://github.com/php/php-src/blob/caf3c64339791e801b8d9c560e0f05d1d94a0f52/Zend/zend_API.h#L101

Here's where the error is thrown in the reflection module:
https://github.com/php/php-src/blob/caf3c64339791e801b8d9c560e0f05d1d94a0f52/ext/reflection/php_reflection.c#L1397

From what I can tell, reflection is getting the default value by looking at the function opcodes, and, well, since internal functions don't have opcodes there is nothing to look at.

I agree that the current behavior of the extension is not optimal, but I don't really see a solution to the problem on my end. I will have to think about it and research some more.

@ihor-sviziev
Copy link
Author

@jbboehr oh... I got it. Do you thing we have some workaround?

@jbboehr
Copy link
Owner

jbboehr commented Nov 5, 2020

If it's not too much trouble, can I see a copy of an invalid generated/code/Psr/Log/LoggerInterface/Proxy.php? I tried looking at the magento repo, but it's not committed.

It pains me to say this, but I don't see a practical workaround on my end. PHP 7's reflection will not report a default value for an internal function.

On the bright side, if I understand it correctly, it appears that PHP has implemented this functionality for PHP 8, so the extension will be correct in PHP 8 if I make the appropriate changes.

@jbboehr
Copy link
Owner

jbboehr commented Nov 5, 2020

For reference, here is the commit to php-src that will allow this to work on PHP 8: php/php-src@3709e74

@jbboehr
Copy link
Owner

jbboehr commented Nov 5, 2020

I hate to be that guy but the PSR interfaces are, like, super frozen so, in theory, you shouldn't have to call reflection on them.

@ihor-sviziev
Copy link
Author

ihor-sviziev commented Nov 5, 2020

@jbboehr In magento we have code generation mechanism that creates "Proxy" class for the original class or interface. The instance of the interface/class will be created through ObjectManager only when some method will be called on proxy.

For creating such Proxy magento uses reflection and it worked fine with all cases, except this one.

Here us two examples:

generated/code/Psr/Log/LoggerInterface/Proxy.php with psr extension
<?php
namespace Psr\Log\LoggerInterface;

/**
 * Proxy class for @see \Psr\Log\LoggerInterface
 */
class Proxy implements \Psr\Log\LoggerInterface, \Magento\Framework\ObjectManager\NoninterceptableInterface
{
    /**
     * Object Manager instance
     *
     * @var \Magento\Framework\ObjectManagerInterface
     */
    protected $_objectManager = null;

    /**
     * Proxied instance name
     *
     * @var string
     */
    protected $_instanceName = null;

    /**
     * Proxied instance
     *
     * @var \Psr\Log\LoggerInterface
     */
    protected $_subject = null;

    /**
     * Instance shareability flag
     *
     * @var bool
     */
    protected $_isShared = null;

    /**
     * Proxy constructor
     *
     * @param \Magento\Framework\ObjectManagerInterface $objectManager
     * @param string $instanceName
     * @param bool $shared
     */
    public function __construct(\Magento\Framework\ObjectManagerInterface $objectManager, $instanceName = '\\Psr\\Log\\LoggerInterface', $shared = true)
    {
        $this->_objectManager = $objectManager;
        $this->_instanceName = $instanceName;
        $this->_isShared = $shared;
    }

    /**
     * @return array
     */
    public function __sleep()
    {
        return ['_subject', '_isShared', '_instanceName'];
    }

    /**
     * Retrieve ObjectManager from global scope
     */
    public function __wakeup()
    {
        $this->_objectManager = \Magento\Framework\App\ObjectManager::getInstance();
    }

    /**
     * Clone proxied instance
     */
    public function __clone()
    {
        $this->_subject = clone $this->_getSubject();
    }

    /**
     * Get proxied instance
     *
     * @return \Psr\Log\LoggerInterface
     */
    protected function _getSubject()
    {
        if (!$this->_subject) {
            $this->_subject = true === $this->_isShared
                ? $this->_objectManager->get($this->_instanceName)
                : $this->_objectManager->create($this->_instanceName);
        }
        return $this->_subject;
    }

    /**
     * {@inheritdoc}
     */
    public function emergency($message, array $context)
    {
        return $this->_getSubject()->emergency($message, $context);
    }

    /**
     * {@inheritdoc}
     */
    public function alert($message, array $context)
    {
        return $this->_getSubject()->alert($message, $context);
    }

    /**
     * {@inheritdoc}
     */
    public function critical($message, array $context)
    {
        return $this->_getSubject()->critical($message, $context);
    }

    /**
     * {@inheritdoc}
     */
    public function error($message, array $context)
    {
        return $this->_getSubject()->error($message, $context);
    }

    /**
     * {@inheritdoc}
     */
    public function warning($message, array $context)
    {
        return $this->_getSubject()->warning($message, $context);
    }

    /**
     * {@inheritdoc}
     */
    public function notice($message, array $context)
    {
        return $this->_getSubject()->notice($message, $context);
    }

    /**
     * {@inheritdoc}
     */
    public function info($message, array $context)
    {
        return $this->_getSubject()->info($message, $context);
    }

    /**
     * {@inheritdoc}
     */
    public function debug($message, array $context)
    {
        return $this->_getSubject()->debug($message, $context);
    }

    /**
     * {@inheritdoc}
     */
    public function log($level, $message, array $context)
    {
        return $this->_getSubject()->log($level, $message, $context);
    }
}
generated/code/Psr/Log/LoggerInterface/Proxy.php without psr extension
<?php
namespace Psr\Log\LoggerInterface;

/**
 * Proxy class for @see \Psr\Log\LoggerInterface
 */
class Proxy implements \Psr\Log\LoggerInterface, \Magento\Framework\ObjectManager\NoninterceptableInterface
{
    /**
     * Object Manager instance
     *
     * @var \Magento\Framework\ObjectManagerInterface
     */
    protected $_objectManager = null;

    /**
     * Proxied instance name
     *
     * @var string
     */
    protected $_instanceName = null;

    /**
     * Proxied instance
     *
     * @var \Psr\Log\LoggerInterface
     */
    protected $_subject = null;

    /**
     * Instance shareability flag
     *
     * @var bool
     */
    protected $_isShared = null;

    /**
     * Proxy constructor
     *
     * @param \Magento\Framework\ObjectManagerInterface $objectManager
     * @param string $instanceName
     * @param bool $shared
     */
    public function __construct(\Magento\Framework\ObjectManagerInterface $objectManager, $instanceName = '\\Psr\\Log\\LoggerInterface', $shared = true)
    {
        $this->_objectManager = $objectManager;
        $this->_instanceName = $instanceName;
        $this->_isShared = $shared;
    }

    /**
     * @return array
     */
    public function __sleep()
    {
        return ['_subject', '_isShared', '_instanceName'];
    }

    /**
     * Retrieve ObjectManager from global scope
     */
    public function __wakeup()
    {
        $this->_objectManager = \Magento\Framework\App\ObjectManager::getInstance();
    }

    /**
     * Clone proxied instance
     */
    public function __clone()
    {
        $this->_subject = clone $this->_getSubject();
    }

    /**
     * Get proxied instance
     *
     * @return \Psr\Log\LoggerInterface
     */
    protected function _getSubject()
    {
        if (!$this->_subject) {
            $this->_subject = true === $this->_isShared
                ? $this->_objectManager->get($this->_instanceName)
                : $this->_objectManager->create($this->_instanceName);
        }
        return $this->_subject;
    }

    /**
     * {@inheritdoc}
     */
    public function emergency($message, array $context = [])
    {
        return $this->_getSubject()->emergency($message, $context);
    }

    /**
     * {@inheritdoc}
     */
    public function alert($message, array $context = [])
    {
        return $this->_getSubject()->alert($message, $context);
    }

    /**
     * {@inheritdoc}
     */
    public function critical($message, array $context = [])
    {
        return $this->_getSubject()->critical($message, $context);
    }

    /**
     * {@inheritdoc}
     */
    public function error($message, array $context = [])
    {
        return $this->_getSubject()->error($message, $context);
    }

    /**
     * {@inheritdoc}
     */
    public function warning($message, array $context = [])
    {
        return $this->_getSubject()->warning($message, $context);
    }

    /**
     * {@inheritdoc}
     */
    public function notice($message, array $context = [])
    {
        return $this->_getSubject()->notice($message, $context);
    }

    /**
     * {@inheritdoc}
     */
    public function info($message, array $context = [])
    {
        return $this->_getSubject()->info($message, $context);
    }

    /**
     * {@inheritdoc}
     */
    public function debug($message, array $context = [])
    {
        return $this->_getSubject()->debug($message, $context);
    }

    /**
     * {@inheritdoc}
     */
    public function log($level, $message, array $context = [])
    {
        return $this->_getSubject()->log($level, $message, $context);
    }
}

Technically we might add some special case if we're generating Proxy class for the \Psr\Log\LoggerInterface and psr extension is enabled - then add default values, but I think that's not right solution.

As a workaround for now we're going to add info that psr extension is causing issues for Magento 2 and recommend not to use it, till the issue will be resolved on any side - in the extension (I see it's possible only since php 8) or on Magento side.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants