Skip to content

Exception thrown in single catch block passes #93

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

Closed
danmooney2 opened this issue Apr 29, 2019 · 21 comments
Closed

Exception thrown in single catch block passes #93

danmooney2 opened this issue Apr 29, 2019 · 21 comments
Assignees
Labels
bug Something isn't working false negative Rule causes false negative findings need to discuss Rule requires discussion

Comments

@danmooney2
Copy link
Member

danmooney2 commented Apr 29, 2019

Preconditions

Steps to reproduce

  • Run phpcs with --standard=Magento2 against file containing multiple catch blocks (it fails as it should):
<?php
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */

namespace Magento;

/**
 * Doer
 */
class Doer
{
    /**
     * Do Something
     */
    public function doSomething()
    {
        try {
            $result = 2;
        } catch (\DummyException $e) {
            throw $e;
        } catch (\NewException $e) {
            throw $e;
        }

        return $result;
    }
}

Run phpcs with --standard=Magento2 against file containing one catch block (it passes when it should not):

<?php
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */

namespace Magento;

/**
 * Doer
 */
class Doer
{
    /**
     * Do Something
     */
    public function doSomething()
    {
        try {
            $result = 2;
        } catch (\DummyException $e) {
            throw $e;
        }

        return $result;
    }
}

Expected result

  • Both files should fail Magento.Exceptions.ThrowCatch rule

Actual result

  • Only file with two catch blocks fails Magento.Exceptions.ThrowCatch rule
@danmooney2 danmooney2 added the bug Something isn't working label Apr 29, 2019
@lenaorobei lenaorobei added the accepted New rule is accepted label May 2, 2019
@larsroettig larsroettig self-assigned this May 6, 2019
@larsroettig
Copy link
Member

Hi @danmooney2,
it is not a Bug the check is for _Exceptions must not be handled in the same function where they are thrown. _. In your example the both thrown the Exceptions out of the function.

@danmooney2
Copy link
Member Author

danmooney2 commented May 8, 2019

Both examples throw all of their exceptions out of the function so both files should pass then? Please elaborate why having 1 catch block fails, while having > 1 catch block that does the same thing passes this rule.

@danmooney2
Copy link
Member Author

danmooney2 commented May 22, 2019

@larsroettig Could you please clarify if the sniff is written properly, and if so, why example 1 passes while example 2 fails?

@lenaorobei
Copy link
Contributor

@larsroettig could you please check this isse? Thank you.

@larsroettig
Copy link
Member

larsroettig commented May 31, 2019 via email

@danmooney2
Copy link
Member Author

@lenaorobei Now that we have mutual agreement on the issue, what is the process for removing buggy sniffs from the ruleset?

@lenaorobei
Copy link
Contributor

I would probably decrease its severity to 0 until it fixed.
Need architectural input here @paliarush.

@paliarush
Copy link
Contributor

If I understood the scenario correctly, it is false negative. In this case we should just report an issue to fix the sniff.

If it is false positive - then it should be removed until fixed.

@danmooney2
Copy link
Member Author

I believe both files referenced above should actually pass the rule; editing the expectation

@larsroettig
Copy link
Member

larsroettig commented Jun 4, 2019 via email

@lenaorobei
Copy link
Contributor

lenaorobei commented Jun 5, 2019

@danmooney2 looking at your examples I would say that sniff should fail in both case. The whole purpose of this rule was to prohibit exceptions handling (catching and doing something) and re-throwing in the same function. Maybe I'm missing something. Could you please explain why do you think those examples should pass?

@lenaorobei lenaorobei added need to discuss Rule requires discussion waiting for feedback Additional explanation needed and removed accepted New rule is accepted labels Jun 5, 2019
@danmooney2
Copy link
Member Author

danmooney2 commented Jun 5, 2019

Can we clarify this rule in the documentation? Should it be: Exceptions must not be thrown from where they are caught? It looks like the sniff is written that way looking at the code in https://github.com/magento/magento-coding-standard/pull/43/files#diff-74e86b8751cc145dc88ad85ae7a8f753R91.

"Exceptions must not be handled in the same function where they are thrown" means, in my mind, that code within catch blocks shouldn't operate on exception objects whatsoever. Obviously, logging the exception has huge benefit, but also beneficial is notifying parent callers when something went wrong (the "dreaded" throw in question). What is the preferred way to notify parent callers? How is nested try/catch exception delegation supposed to work (and be fixed, for that matter)?

\Magento\Framework\App\Bootstrap::run, the main entrance point for all app requests, violates this rule, as well as hundreds of other methods in Magento (not defending them, just highlighting ubiquity).

This needs some clear documentation (not just 13 words) and steps to fix with example code, as it is slowing down our code deliveries because we run into this sniff being violated almost on a daily basis for existing files we edit and we were told not to ignore them.

Either way, the sniff is a false positive or false negative and needs attention. If it is indeed a false positive, we'll try to work around it without disabling.

@danmooney2
Copy link
Member Author

danmooney2 commented Jun 6, 2019

I reverted back to original issue description/expectation (false positive). I changed it out of a knee jerk fear of having to do even more refactoring of something I don't completely understand 😨, apologies.

@danmooney2
Copy link
Member Author

danmooney2 commented Jun 6, 2019

"Exceptions MUST NOT be rethrown."

When read like that the rule makes much more sense. We're conflating handling and throwing: handling is not always throwing, but throwing is always handling.

@lenaorobei lenaorobei added false negative Rule causes false negative findings and removed waiting for feedback Additional explanation needed labels Jun 6, 2019
@lenaorobei
Copy link
Contributor

lenaorobei commented Jun 6, 2019

@danmooney2 I've talked with @paliarush regarding exceptions handling best practices. The main points are:

  • Exceptions must not be used for conditional logic.
  • Each application layer must use its own exceptions scope, e.g.:
function doSomething()
{
    try {
        // do something
    } catch (\LowerLayerException $e) {
        // do something if needed
        throw new \MyLayerException;
    return $result;
}

Hope it makes sense.

@danmooney2
Copy link
Member Author

danmooney2 commented Jun 6, 2019

OK I understand not rethrowing caught exceptions in the same function (that's entirely what the sniff is doing). Unless the sniff gets updated to cover everything you just mentioned, I still petition to change the wording of the rule from "handling" to "throwing" for those unlucky individuals who interpret handling as simply having the existence of a catch block.

@danmooney2
Copy link
Member Author

Also @lenaorobei I thought we agreed this is a false positive; both examples should fail.

@lenaorobei
Copy link
Contributor

Both examples should fail, but only one fails and it's false negative. In case if sniff raises warnings when it shouldn't - it's false positive.

@larsroettig
Copy link
Member

larsroettig commented Jun 11, 2019

Hi, @lenaorobei @paliarush,
but I think these 2 examples should be okay.

Exsample1:

<?php
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */

namespace Magento;


class UserRepo
    /**
     * @param int $id
     * @throws \Magento\Framework\Exception\NotFoundException
     */
    public function load(int $id)
    {
        /// some logic
        throw new \Magento\Framework\Exception\NotFoundException('We cant load UserData');
    }
}

class UserViewHelper
{
    /** @var UserRepo */
    private $userRepo;

    /**
     * @param UserRepo $userRepo
     */
    public function __construct(UserRepo $userRepo)
    {
        $this->userRepo = $userRepo;
    }

    /**
     * @param int $id
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function execute(int $id)
    {
        try
        {
          $this->userRepo->load($id);
        }catch (\Exception $exception)
        {
            throw new Magento\Framework\Exception\LocalizedException('Nice user friendly message');
        }
    }
}

Exsample2:

<?php
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */

namespace Magento;


use Exception;
use Magento\Framework\Exception\LocalizedException;
use NotFoundException;

class UserRepo
{
    /**
     * @param int $id
     * @throws Framework\Exception\NotFoundException
     */
    public function load(int $id)
    {
        /// some logic
        throw new Framework\Exception\NotFoundException('We cant load UserData');
    }
}

class UserViewHelper
{
    /** @var UserRepo */
    private $userRepo;

    /**
     * @param UserRepo $userRepo
     */
    public function __construct(UserRepo $userRepo)
    {
        $this->userRepo = $userRepo;
    }

    /**
     * @param int $id
     * @throws LocalizedException
     */
    public function execute(int $id)
    {
        try {
            $this->userRepo->load($id);
        } catch (NotFoundException $exception) {
            throw new Magento\Framework\Exception\LocalizedException('Nice user friendly message case 1');
        } catch (Exception $exception) {
            throw new Magento\Framework\Exception\LocalizedException('Nice user friendly message case 2');
        }
    }
}

Main Use case you use a Libray like Zend and must wrap a LowerLayerException for security reasons also the library exception are not translated for the frontend than try and catch should be okay in my opinion.

@lenaorobei
Copy link
Contributor

@larsroettig both examples should be fine since it is different exception type.

@lenaorobei
Copy link
Contributor

#120

magento-devops-reposync-svc pushed a commit that referenced this issue Oct 6, 2021
…agento-coding-standard-306

[Imported] AC-681: Create phpcs static check for PhtmlTemplateTest
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working false negative Rule causes false negative findings need to discuss Rule requires discussion
Projects
None yet
Development

No branches or pull requests

4 participants