Skip to content

Conversation

@yuriyDne
Copy link

@yuriyDne yuriyDne commented Jun 6, 2018

Handle type errors when object is creating by ObjectManager

Description

When object creates with type error on production mode with display_errors off it's very difficult to catch this error because Magento doesn't handle it in log files (only apache log which is not always public accessed). It can be actual for background processes when there is no possibility for manual testing. So my suggestion is catch object creating TypeError (as example) and throw it as simple Exception.

Fixed Issues (if relevant)

Manual testing scenarios

Contribution checklist

  • [x ] Pull request has a meaningful description of its purpose
  • [x ] All commits are accompanied by meaningful commit messages
  • All new or changed code is covered with unit/integration tests (if applicable)
  • All automated tests passed successfully (all builds on Travis CI are green)

@magento-cicd2
Copy link
Contributor

magento-cicd2 commented Jun 6, 2018

CLA assistant check
All committers have signed the CLA.

@magento-engcom-team magento-engcom-team added Partner: Atwix Pull Request is created by partner Atwix partners-contribution Pull Request is created by Magento Partner Component: Framework/ObjectManager labels Jun 6, 2018
Copy link
Contributor

@ishakhsuvarov ishakhsuvarov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's best to cover this kind of changes with a test

try {
return new $type(...array_values($args));
} catch (\Throwable $e) {
throw new \Exception($e->getMessage(), null, $e);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest throwing \Magento\Framework\Exception\RuntimeException here instead, as \Exception is too generic.

try {
return new $type(...array_values($args));
} catch (\Throwable $e) {
throw new \Exception($e->getMessage(), null, $e);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to add a log entry right here

@ishakhsuvarov ishakhsuvarov self-assigned this Jun 6, 2018
try {
return new $type(...array_values($args));
} catch (\Throwable $e) {
$this->getLogger()->critical(__(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since framework must remain independent of other parts of the system __() call can not be used here. It's advised to use Magento\Framework\Phrase instead.

$e->getMessage()
]
));
throw new RuntimeException(__($e->getMessage()));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Original exception message is already logged at this point. It makes sense to throw more generic message here.

@ishakhsuvarov
Copy link
Contributor

@yuriyDne Thanks for the update. This issue is apparently more complicated then it looks.
Please let us know if you need any help with it.

@yuriyDne
Copy link
Author

Sorry for delay - you are right - it's much more complicated than I expected when we try to use logger instance
I thought the best way here with minimal code changes and risks is just throw an Exception (or Runtime Exception as you advised). If you still want add log entry here - I need you help because I'm just a beginner in Magento and can miss some important cases

@ishakhsuvarov
Copy link
Contributor

@yuriyDne Sure, we are happy to help. We'll take a closer look and let you know

try {
return new $type(...array_values($args));
} catch (\Throwable $e) {
$this->getLogger()->critical(__(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be better, to replace getLogger method with a static object manager call, getting LoggerInterface right here, for example:

use Magento\Framework\App\ObjectManager;
use Psr\Log\LoggerInterface;

$logger = ObjectManager::getInstance()->get(LoggerInterface::class);

This way it's possible not to rely on $this->objectManager which may or may not be set, making the code slightly more future-proof.

@ishakhsuvarov
Copy link
Contributor

Hi @yuriyDne

I've updated the code review with possible solution. Please let me know if you have any questions.

@yuriyDne
Copy link
Author

@ishakhsuvarov - Thanks for help - I updated code according to your recommendations
The one moment - now it's impossible to cover createObject method with unit test now because of ObjectManager::getInstance() - am I right? Or there is a possibility to write phpunit even in such case?

@ishakhsuvarov
Copy link
Contributor

@yuriyDne I would suggest covering this with an integration test, which would be much more effective in this case.
You may try updating or basing a new test on this one: \Magento\Framework\ObjectManager\ObjectManagerTest.

@yuriyDne
Copy link
Author

yuriyDne commented Jun 18, 2018

@ishakhsuvarov integration test is implemented. Please check. Thanks

return new $type(...array_values($args));
try {
return new $type(...array_values($args));
} catch (\Throwable $e) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have executed full suite of integration tests on the internal CICD and found out that, catching \Throwable here yields undesired results.
This primarily happens, when some classes throw different exceptions right from the constructor. While this violates Magento Technical Guidelines, this case still has to be supported, for example:

  • \Magento\Framework\Filesystem\File\Read would throw FileSystemException from the constructor when file does not exist.
  • AbstractFactory would then catch it, as \Throwable catches all possible exception types, and rethrow it differently.
  • FileSystem logic broken as a result

To bypass this case I would suggest catching only \TypeError here, but not \Throwable.

I am attaching list of integration tests, which fail with Throwable and pass with TypeError:

Magento.Framework.Communication.ConfigTest.testGetTopicsNumeric Magento.Framework.Communication.ConfigTest.testGetTopicsNumericInvalid Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionMissingRequest Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionNotExistingServiceMethod Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionNotExistingService Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionNoAttributes Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionInvalidResponseSchema Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionInvalidRequestSchema Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionMultipleHandlersSynchronousMode Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionInvalidHandler Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionInvalidTopicNameInEnv Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionTopicWithoutDataInEnv Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionTopicWithMissedKeysInEnv Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionTopicWithExcessiveKeysInEnv Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionTopicWithNonMatchedNameInEnv Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionMultipleHandlersSynchronousModeInEnv Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionInvalidRequestSchemaInEnv Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionInvalidResponseSchemaInEnv Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionInvalidMethodInHandlerInEnv Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionWithDisabledHandlerInEnv Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionIncorrectRequestSchemaTypeInEnv Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionIsNotBooleanTypeOfIsSynchronousInEnv Magento.Framework.Communication.ConfigTest.testGetTopicsNumeric Magento.Framework.Communication.ConfigTest.testGetTopicsNumericInvalid Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionMissingRequest Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionNotExistingServiceMethod Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionNotExistingService Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionNoAttributes Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionInvalidResponseSchema Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionInvalidRequestSchema Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionMultipleHandlersSynchronousMode Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionInvalidHandler Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionInvalidTopicNameInEnv Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionTopicWithoutDataInEnv Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionTopicWithMissedKeysInEnv Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionTopicWithExcessiveKeysInEnv Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionTopicWithNonMatchedNameInEnv Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionMultipleHandlersSynchronousModeInEnv Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionInvalidRequestSchemaInEnv Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionInvalidResponseSchemaInEnv Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionInvalidMethodInHandlerInEnv Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionWithDisabledHandlerInEnv Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionIncorrectRequestSchemaTypeInEnv Magento.Framework.Communication.ConfigTest.testGetTopicsExceptionIsNotBooleanTypeOfIsSynchronousInEnv Magento.Framework.Filesystem.File.ReadTest.testAssertValid with data set #0 Magento.Framework.Filesystem.File.ReadTest.testAssertValid with data set #0 Magento.Framework.Filesystem.File.WriteTest.testFileExistException with data set #0 Magento.Framework.Filesystem.File.WriteTest.testFileExistException with data set #1 Magento.Framework.Filesystem.File.WriteTest.testFileExistException with data set #0 Magento.Framework.Filesystem.File.WriteTest.testFileExistException with data set #1

);

throw new RuntimeException(
new Phrase('Create object error')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest to make error message a bit more detailed, something in the lines of original TypeError

// Call parent constructor without parameters to generate error
parent::__construct();
}
} No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add an empty line at the end of the file, as it is required by the coding standard.


class ConstructorWithThrowable extends \Magento\Framework\ObjectManager\TestAsset\ConstructorOneArgument
{
public function __construct(Basic $one)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Static tests would fail here, as $one is never used.
I'd suggest adding

/**
 * @SuppressWarnings(PHPMD.UnusedLocalVariable)`
 */

public function testNewInstanceWithThrowableError()
{
try {
$testObject = self::$_objectManager->create(self::TEST_CLASS_WITH_THROWABLE);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no need to use $testObject variable. You may just do an ObjectManager call to get the exception.

/**
* Test create instance with throwable error
*/
public function testNewInstanceWithThrowableError()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's best to avoid doing try/catch in the test.
You may use

/**
 * @expectedException \TypeError
 */

It would assert that correct exception is thrown automatically.

@ishakhsuvarov
Copy link
Contributor

Hi @yuriyDne
Unfortunately Travis CI Builds queue is processed slowly at the moment. I've executed internal CICD test suite on this PR and reflected required changes in the review.

Looks like this PR is nearly complete now! :)

@yuriyDne
Copy link
Author

@ishakhsuvarov - made fixes after your code review. Please check changes. Thanks

@ishakhsuvarov
Copy link
Contributor

@yuriyDne Thank you for the update

 - Added declaration of strict types
@magento-engcom-team
Copy link
Contributor

Hi @yuriyDne. Thank you for your contribution.
We will aim to release these changes as part of 2.3.0.
Please check the release notes for final confirmation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants