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

Xdebug: Maximum function nesting level reached #117

Closed
LaurentEsc opened this issue Sep 8, 2016 · 10 comments
Closed

Xdebug: Maximum function nesting level reached #117

LaurentEsc opened this issue Sep 8, 2016 · 10 comments

Comments

@LaurentEsc
Copy link

LaurentEsc commented Sep 8, 2016

Hello,

I am having trouble running my PHPunit tests with xdebug on, and I think the problem might come from this package.

Until a recent change in my code, I had no problems running tests while my models were already using Eloquence. But today, I added 4 classes that inherit my Product model to provide different logics according to the type of product (Ex: AboProduct, StandardProduct...), which I think improves my code.

Here is how my Product model looks like:

namespace App;

use Illuminate\Database\Eloquent\Model;
use Sofa\Eloquence\Eloquence;
use Sofa\Eloquence\Mappable;

class Product extends Model
{
    use Eloquence, Mappable;

    protected $maps = [
        'name' => 'OLD_NAME',
        'description' => 'OLD_DESCRIPTION',
        'type' => 'OLD_TYPE',
    ];

    /**
     * Return an instance of a concrete product class
     * 
     * @return ProductContract
     */
    public function getConcrete()
    {
        $class = $this->resolveProductClass(); // Ex: 'App/AboProduct'

        $model = new $class;
        $model->exists = true;
        $model->setRawAttributes((array) $this->getAttributes(), true);

        return $model;
    }
}

Here is how a concrete product class looks like:

namespace App;

class AboProduct extends Product implements ProductContract
{
    public function performComplexCalculation()
    {
        // The ProductContract interface requires us to implement this
    }
}

Here is how my controller looks like

namespace App\Http\Controllers;

use App\Product;

class ProductController extends Controller
{
    public function getProductCalculation(Product $product)
    {
        return $product->getConcrete()->performComplexCalculation();
    }
}

Now, as written above, I am having problems during my tests. Otherwise, everything works as intended. My tests are actually green and run without problem when I run them individually or filtered. It seems that the problem only happens when I run a large part of my test suite.

Here is the error that I get:

Fatal error: Maximum function nesting level of '256' reached, aborting! in /var/www/html/vendor/sofa/hookable/src/ArgumentBag.php on line 55

And here an extract of the stack trace (the last traces before it fails):

...
27.2507   75173272 200. App\AboProduct->Sofa\Eloquence\Mappable\{closure}() /var/www/html/vendor/sofa/hookable/src/Pipeline.php:84
27.2507   75173272 201. Sofa\Hookable\Pipeline->Sofa\Hookable\{closure}() /var/www/html/vendor/sofa/eloquence/src/Mappable/Hooks.php:111
27.2507   75173336 202. App\AboProduct->Sofa\Eloquence\Mappable\{closure}() /var/www/html/vendor/sofa/hookable/src/Pipeline.php:84
27.2507   75173336 203. Sofa\Hookable\Pipeline->Sofa\Hookable\{closure}() /var/www/html/vendor/sofa/eloquence/src/Mappable/Hooks.php:111
27.2507   75173400 204. App\AboProduct->Sofa\Eloquence\Mappable\{closure}() /var/www/html/vendor/sofa/hookable/src/Pipeline.php:84
27.2507   75173400 205. Sofa\Hookable\Pipeline->Sofa\Hookable\{closure}() /var/www/html/vendor/sofa/eloquence/src/Mappable/Hooks.php:111
27.2507   75173464 206. App\AboProduct->Sofa\Eloquence\Mappable\{closure}() /var/www/html/vendor/sofa/hookable/src/Pipeline.php:84
27.2507   75173464 207. Sofa\Hookable\Pipeline->Sofa\Hookable\{closure}() /var/www/html/vendor/sofa/eloquence/src/Mappable/Hooks.php:111
27.2507   75173528 208. App\AboProduct->Sofa\Eloquence\Mappable\{closure}() /var/www/html/vendor/sofa/hookable/src/Pipeline.php:84
27.2507   75173528 209. Sofa\Hookable\Pipeline->Sofa\Hookable\{closure}() /var/www/html/vendor/sofa/eloquence/src/Mappable/Hooks.php:111
27.2507   75173592 210. App\AboProduct->Sofa\Eloquence\Mappable\{closure}() /var/www/html/vendor/sofa/hookable/src/Pipeline.php:84
27.2507   75173592 211. Sofa\Hookable\Pipeline->Sofa\Hookable\{closure}() /var/www/html/vendor/sofa/eloquence/src/Mappable/Hooks.php:111
27.2507   75173656 212. App\AboProduct->Sofa\Eloquence\Mappable\{closure}() /var/www/html/vendor/sofa/hookable/src/Pipeline.php:84
27.2507   75173656 213. Sofa\Hookable\Pipeline->Sofa\Hookable\{closure}() /var/www/html/vendor/sofa/eloquence/src/Mappable/Hooks.php:111
27.2507   75173720 214. App\AboProduct->Sofa\Eloquence\Mappable\{closure}() /var/www/html/vendor/sofa/hookable/src/Pipeline.php:84
27.2507   75173720 215. Sofa\Hookable\Pipeline->Sofa\Hookable\{closure}() /var/www/html/vendor/sofa/eloquence/src/Mappable/Hooks.php:111
27.2507   75173784 216. App\AboProduct->Sofa\Eloquence\Mappable\{closure}() /var/www/html/vendor/sofa/hookable/src/Pipeline.php:84
27.2507   75173784 217. Sofa\Hookable\Pipeline->Sofa\Hookable\{closure}() /var/www/html/vendor/sofa/eloquence/src/Mappable/Hooks.php:111
27.2507   75173848 218. App\AboProduct->Sofa\Eloquence\Mappable\{closure}() /var/www/html/vendor/sofa/hookable/src/Pipeline.php:84
27.2507   75173848 219. Sofa\Hookable\Pipeline->Sofa\Hookable\{closure}() /var/www/html/vendor/sofa/eloquence/src/Mappable/Hooks.php:111
27.2507   75173912 220. App\AboProduct->Sofa\Eloquence\Mappable\{closure}() /var/www/html/vendor/sofa/hookable/src/Pipeline.php:84
27.2507   75173912 221. Sofa\Hookable\Pipeline->Sofa\Hookable\{closure}() /var/www/html/vendor/sofa/eloquence/src/Mappable/Hooks.php:111
27.2507   75173976 222. App\AboProduct->Sofa\Eloquence\Mappable\{closure}() /var/www/html/vendor/sofa/hookable/src/Pipeline.php:84
27.2507   75173976 223. Sofa\Hookable\Pipeline->Sofa\Hookable\{closure}() /var/www/html/vendor/sofa/eloquence/src/Mappable/Hooks.php:111
27.2507   75174040 224. App\AboProduct->Sofa\Eloquence\Mappable\{closure}() /var/www/html/vendor/sofa/hookable/src/Pipeline.php:84
27.2507   75174040 225. Sofa\Hookable\Pipeline->Sofa\Hookable\{closure}() /var/www/html/vendor/sofa/eloquence/src/Mappable/Hooks.php:111
27.2507   75174104 226. App\AboProduct->Sofa\Eloquence\Mappable\{closure}() /var/www/html/vendor/sofa/hookable/src/Pipeline.php:84
27.2507   75174104 227. Sofa\Hookable\Pipeline->Sofa\Hookable\{closure}() /var/www/html/vendor/sofa/eloquence/src/Mappable/Hooks.php:111
27.2508   75174168 228. App\AboProduct->Sofa\Eloquence\Mappable\{closure}() /var/www/html/vendor/sofa/hookable/src/Pipeline.php:84
27.2508   75174168 229. Sofa\Hookable\Pipeline->Sofa\Hookable\{closure}() /var/www/html/vendor/sofa/eloquence/src/Mappable/Hooks.php:111
27.2508   75174232 230. App\AboProduct->Sofa\Eloquence\Mappable\{closure}() /var/www/html/vendor/sofa/hookable/src/Pipeline.php:84
27.2508   75174232 231. Sofa\Hookable\Pipeline->Sofa\Hookable\{closure}() /var/www/html/vendor/sofa/eloquence/src/Mappable/Hooks.php:111
27.2508   75174296 232. App\AboProduct->Sofa\Eloquence\Mappable\{closure}() /var/www/html/vendor/sofa/hookable/src/Pipeline.php:84
27.2508   75174296 233. Sofa\Hookable\Pipeline->Sofa\Hookable\{closure}() /var/www/html/vendor/sofa/eloquence/src/Mappable/Hooks.php:111
27.2508   75174360 234. App\AboProduct->Sofa\Eloquence\Mappable\{closure}() /var/www/html/vendor/sofa/hookable/src/Pipeline.php:84
27.2508   75174360 235. Sofa\Hookable\Pipeline->Sofa\Hookable\{closure}() /var/www/html/vendor/sofa/eloquence/src/Mappable/Hooks.php:111
27.2508   75174424 236. App\AboProduct->Sofa\Eloquence\Mappable\{closure}() /var/www/html/vendor/sofa/hookable/src/Pipeline.php:84
27.2508   75174424 237. Sofa\Hookable\Pipeline->Sofa\Hookable\{closure}() /var/www/html/vendor/sofa/eloquence/src/Mappable/Hooks.php:111
27.2508   75174488 238. App\AboProduct->Sofa\Eloquence\Mappable\{closure}() /var/www/html/vendor/sofa/hookable/src/Pipeline.php:84
27.2508   75174488 239. Sofa\Hookable\Pipeline->Sofa\Hookable\{closure}() /var/www/html/vendor/sofa/eloquence/src/Mappable/Hooks.php:111
27.2508   75174552 240. App\AboProduct->Sofa\Eloquence\Mappable\{closure}() /var/www/html/vendor/sofa/hookable/src/Pipeline.php:84
27.2508   75174552 241. Sofa\Hookable\Pipeline->Sofa\Hookable\{closure}() /var/www/html/vendor/sofa/eloquence/src/Mappable/Hooks.php:111
27.2508   75174616 242. App\AboProduct->Sofa\Eloquence\Mappable\{closure}() /var/www/html/vendor/sofa/hookable/src/Pipeline.php:84
27.2508   75174616 243. Sofa\Hookable\Pipeline->Sofa\Hookable\{closure}() /var/www/html/vendor/sofa/eloquence/src/Mappable/Hooks.php:111
27.2508   75174680 244. App\AboProduct->Sofa\Eloquence\Mappable\{closure}() /var/www/html/vendor/sofa/hookable/src/Pipeline.php:84
27.2508   75174680 245. Sofa\Hookable\Pipeline->Sofa\Hookable\{closure}() /var/www/html/vendor/sofa/eloquence/src/Mappable/Hooks.php:111
27.2508   75174744 246. App\AboProduct->Sofa\Eloquence\Mappable\{closure}() /var/www/html/vendor/sofa/hookable/src/Pipeline.php:84
27.2508   75174744 247. Sofa\Hookable\Pipeline->Sofa\Hookable\{closure}() /var/www/html/vendor/sofa/eloquence/src/Mappable/Hooks.php:111
27.2508   75174808 248. App\AboProduct->Sofa\Eloquence\Mappable\{closure}() /var/www/html/vendor/sofa/hookable/src/Pipeline.php:84
27.2508   75174808 249. Sofa\Hookable\Pipeline->Sofa\Hookable\{closure}() /var/www/html/vendor/sofa/eloquence/src/Mappable/Hooks.php:111
27.2508   75174872 250. App\AboProduct->Sofa\Eloquence\Mappable\{closure}() /var/www/html/vendor/sofa/hookable/src/Pipeline.php:84
27.2508   75174872 251. Sofa\Hookable\Pipeline->Sofa\Hookable\{closure}() /var/www/html/vendor/sofa/eloquence/src/Mappable/Hooks.php:111
27.2508   75174936 252. App\AboProduct->Sofa\Eloquence\Mappable\{closure}() /var/www/html/vendor/sofa/hookable/src/Pipeline.php:84
27.2508   75174936 253. Sofa\Hookable\Pipeline->Sofa\Hookable\{closure}() /var/www/html/vendor/sofa/eloquence/src/Mappable/Hooks.php:111
27.2508   75175000 254. App\AboProduct->Sofa\Eloquence\Mappable\{closure}() /var/www/html/vendor/sofa/hookable/src/Pipeline.php:84
27.2508   75175000 255. Sofa\Hookable\Pipeline->Sofa\Hookable\{closure}() /var/www/html/vendor/sofa/eloquence/src/Mappable/Hooks.php:111
27.2508   75175064 256. App\AboProduct->Sofa\Eloquence\Mappable\{closure}() /var/www/html/vendor/sofa/hookable/src/Pipeline.php:84

I don't really know what is going on, but seeing this stack trace I think here could be the right place to ask. I also suspect this might be related to #96 of @maltsev;

Hopefully someone can help :) Thanks

Please note that I'm using the v5.2.5 of Eloquence.

@Barcelonczyk
Copy link

Barcelonczyk commented Sep 22, 2016

Set xdebug.max_nesting_level = 500 in php.ini at the end of [XDebug] section.

Remember to restart your server after saving php.ini file.

@LaurentEsc
Copy link
Author

LaurentEsc commented Sep 22, 2016

Hi @Barcelonczyk and thank you for commenting.

This is how I solved the problem for now ... I increased the xdebug's max_nesting_level.
The stack trace above still makes me think there might be a problem in the package ...

@jarektkaczyk
Copy link
Owner

@LaurentEsc stacktrace seems like endless recurrency. Have you change anything or only increased max_nesting_level setting?

@LaurentEsc
Copy link
Author

Hi @jarektkaczyk

No I didn't change anything. Increasing the max_nesting_level to 400 solved the problem temporary. the problem actually just occurred again as my test suite grew to 77 tests, 203 assertions, and I had to increase the max_nesting_level to 500.

The stack trace indeed looks like an endless recurrency, but I don't think it is the case: my tests actually run green without error when I increase the limit. they would of course fail if there was some kind of endless loop somewhere...

I honestly don't know what is going on and why the tests are reaching this xdebug limit after so many calls of Pipeline and Mappable/Hooks.

It also really seems to depend on the size of the test suite and to get worse as it grows.

@LaurentEsc
Copy link
Author

I will try to take a better look this week and see if I'm using the package incorrectly. Maybe I am doing something wrong? Please let me know if you think of any misuse that could lead to my problem.

@hughgrigg
Copy link

Also seem to be hitting this issue. Increasing the max nesting level does not seem like a good solution. Why does the infinite loop occur at all, and why only once the test-suite reaches a certain size?

I can get the test in question to pass alone or in a smaller set; the function nesting error only occurs when it is run after other tests.

@lemasson-h
Copy link

If some people wonders how to fix it without having to change the limit of nested level, where what to do on the tearDown:

use Illuminate\Database\Eloquent\Model;
...
        foreach (get_declared_classes() as $class) {
            $reflection = new \ReflectionClass($class);
            if (!$reflection->isAbstract()
                && is_subclass_of($class, Model::class)
                && $reflection->hasMethod('flushHooks')
                && !in_array(Mockery\MockInterface::class, class_implements($class))
            ) {
                $class::flushHooks();
            }
        }

Explanation: when you are using sofa/eloquence on your models it adds something calls $hooks.

For example if you are using Mappable: Sofa\EloquenceMappable look at bootMappable method. It's adding hooks and if you look how they add them:

        static::$hooks[$method][] = $hook;

The problem is, it is static and it stacks instead of replacing the previous values, and later they are using it to loop through it and call all the hooks of a method.

So every time you do a new Model(), new query etc, it cumulates hooks and nothing is resetting it.

So the more tests you are adding, without resetting the hooks for each class which are using Sofa\Hookable\Hookable, the more nested called you will have.

One last thing as you are on a test, you want to avoid to call the flushHook on class which are mock, that's why I have added this condition:

 !in_array(Mockery\MockInterface::class, class_implements($class))

If you are not using Mockery, you can remove it. If you are using another lib for the stub/mock, replace Mockery\MockInterface::class by the lower class name which defines it is a stub/mock.

@hughgrigg
Copy link

It seems you also need to run $class::bootMappable(); after $class::flushHooks();. Similarly, you need $class::bootMutable(); if it's using the Mutable trait, and so on.

@amochohan
Copy link

amochohan commented Oct 21, 2017

Was this ever fixed in core? The solution presented above may resolve in certain test scenarios but it looks like it might be preventable in the first place. I've got a test suite of ~1000 tests, 5000 assertions and it's not possible for me to even run all tests anymore. Using a bash script to run one-by-one I can execute each test, but as a suite, I can see that the hooks are being carried over from one test to the next.

travisaustin pushed a commit to travisaustin/eloquence-mappable that referenced this issue Jul 16, 2020
Prevent the trait from booting each time a class is instantiated. See jarektkaczyk/eloquence#117 for description of the issue this resolves.
@travisaustin
Copy link

travisaustin commented Jul 16, 2020

I was having a similar issue with my testing. After spending more time than I'd like to admit chasing down the issue, I found this thread and the explanation from @lemasson-h was very helpful.

Continually increasing xdebug.max_nesting_level is not a satisfactory solution to me. Since this trait is adding new hooks each time a class is instantiated, the performance of larger projects will be negatively impacted by the current behavior.

Please see the PR I submitted above. With this fix, my own environment continues to function well. Also, my tests complete in less than 30% of the time they used to take!

I'm open to feedback, but I suspect this may fix the issue.

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

7 participants