cms icon indicating copy to clipboard operation
cms copied to clipboard

API call (collection/foo/entries) results in "Maximum stack depth exceeded"

Open promanapeople opened this issue 3 years ago • 5 comments

Bug description

Under certain conditions, a collection with parent/child relationships can trigger a 500 Maximum stack depth exceeded if queried via API.

How to reproduce

  1. Create a collection
  2. Set "expect a root page" in the collection config
  3. Set the ordering to be date based (desc)
  4. Create 2 blueprints. One for the root page (eg "recursion_test_root") and one for the other pages (eg "recursion_test_post")
  5. In the root blueprint, add an "Entries" relationship field
  6. Configure the Entries field to accept max 1 entry from the "recursion_test_post" collection
  7. Create the root page
  8. Create a post "Post A" (this post uses the "recursion_test_post" blueprint
  9. Edit the Root page. Select "Post A" in the Entries field

image

Make an api call to /collections/recursion_test/entries?fields=title,parent&limit=4

500 error should result

Logs

[2022-07-14 01:01:00] local.ERROR: Maximum stack depth exceeded {"exception":"[object] (InvalidArgumentException(code: 0): Maximum stack depth exceeded at D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Http\\JsonResponse.php:84)
[stacktrace]
#0 D:\\xampp\\htdocs\\recursion_test\\vendor\\symfony\\http-foundation\\JsonResponse.php(54): Illuminate\\Http\\JsonResponse->setData(Array)
#1 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Http\\JsonResponse.php(32): Symfony\\Component\\HttpFoundation\\JsonResponse->__construct(Array, 200, Array, false)
#2 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\ResponseFactory.php(99): Illuminate\\Http\\JsonResponse->__construct(Array, 200, Array, 0)
#3 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Http\\Resources\\Json\\PaginatedResourceResponse.php(26): Illuminate\\Routing\\ResponseFactory->json(Array, 200)
#4 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Http\\Resources\\Json\\ResourceCollection.php(134): Illuminate\\Http\\Resources\\Json\\PaginatedResourceResponse->toResponse(Object(Illuminate\\Http\\Request))
#5 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Http\\Resources\\Json\\ResourceCollection.php(114): Illuminate\\Http\\Resources\\Json\\ResourceCollection->preparePaginatedResponse(Object(Illuminate\\Http\\Request))
#6 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Router.php(776): Illuminate\\Http\\Resources\\Json\\ResourceCollection->toResponse(Object(Illuminate\\Http\\Request))
#7 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Router.php(763): Illuminate\\Routing\\Router::toResponse(Object(Illuminate\\Http\\Request), Object(Illuminate\\Http\\Resources\\Json\\AnonymousResourceCollection))
#8 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Router.php(695): Illuminate\\Routing\\Router->prepareResponse(Object(Illuminate\\Http\\Request), Object(Illuminate\\Http\\Resources\\Json\\AnonymousResourceCollection))
#9 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php(128): Illuminate\\Routing\\Router->Illuminate\\Routing\\{closure}(Object(Illuminate\\Http\\Request))
#10 D:\\xampp\\htdocs\\recursion_test\\vendor\\statamic\\cms\\src\\Http\\Middleware\\HandleToken.php(13): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#11 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php(167): Statamic\\Http\\Middleware\\HandleToken->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#12 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Middleware\\SubstituteBindings.php(50): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#13 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php(167): Illuminate\\Routing\\Middleware\\SubstituteBindings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#14 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Middleware\\ThrottleRequests.php(127): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#15 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Middleware\\ThrottleRequests.php(103): Illuminate\\Routing\\Middleware\\ThrottleRequests->handleRequest(Object(Illuminate\\Http\\Request), Object(Closure), Array)
#16 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Middleware\\ThrottleRequests.php(55): Illuminate\\Routing\\Middleware\\ThrottleRequests->handleRequestUsingNamedLimiter(Object(Illuminate\\Http\\Request), Object(Closure), 'api', Object(Closure))
#17 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php(167): Illuminate\\Routing\\Middleware\\ThrottleRequests->handle(Object(Illuminate\\Http\\Request), Object(Closure), 'api')
#18 D:\\xampp\\htdocs\\recursion_test\\vendor\\statamic\\cms\\src\\API\\Middleware\\Cache.php(26): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#19 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php(167): Statamic\\API\\Middleware\\Cache->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#20 D:\\xampp\\htdocs\\recursion_test\\vendor\\statamic\\cms\\src\\Http\\Middleware\\RequireStatamicPro.php(17): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#21 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php(167): Statamic\\Http\\Middleware\\RequireStatamicPro->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#22 D:\\xampp\\htdocs\\recursion_test\\vendor\\statamic\\cms\\src\\Http\\Middleware\\SwapExceptionHandler.php(19): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#23 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php(167): Statamic\\Http\\Middleware\\SwapExceptionHandler->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#24 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php(103): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#25 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Router.php(697): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#26 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Router.php(672): Illuminate\\Routing\\Router->runRouteWithinStack(Object(Illuminate\\Routing\\Route), Object(Illuminate\\Http\\Request))
#27 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Router.php(636): Illuminate\\Routing\\Router->runRoute(Object(Illuminate\\Http\\Request), Object(Illuminate\\Routing\\Route))
#28 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Router.php(625): Illuminate\\Routing\\Router->dispatchToRoute(Object(Illuminate\\Http\\Request))
#29 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Kernel.php(166): Illuminate\\Routing\\Router->dispatch(Object(Illuminate\\Http\\Request))
#30 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php(128): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}(Object(Illuminate\\Http\\Request))
#31 D:\\xampp\\htdocs\\recursion_test\\vendor\\statamic\\cms\\src\\Http\\Middleware\\DisableFloc.php(18): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#32 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php(167): Statamic\\Http\\Middleware\\DisableFloc->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#33 D:\\xampp\\htdocs\\recursion_test\\vendor\\statamic\\cms\\src\\Http\\Middleware\\CheckMultisite.php(14): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#34 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php(167): Statamic\\Http\\Middleware\\CheckMultisite->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#35 D:\\xampp\\htdocs\\recursion_test\\vendor\\statamic\\cms\\src\\Http\\Middleware\\CheckComposerJsonScripts.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#36 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php(167): Statamic\\Http\\Middleware\\CheckComposerJsonScripts->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#37 D:\\xampp\\htdocs\\recursion_test\\vendor\\statamic\\cms\\src\\Http\\Middleware\\PoweredByHeader.php(19): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#38 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php(167): Statamic\\Http\\Middleware\\PoweredByHeader->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#39 D:\\xampp\\htdocs\\recursion_test\\vendor\\barryvdh\\laravel-debugbar\\src\\Middleware\\InjectDebugbar.php(60): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#40 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php(167): Barryvdh\\Debugbar\\Middleware\\InjectDebugbar->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#41 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#42 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull.php(31): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#43 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php(167): Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#44 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#45 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Middleware\\TrimStrings.php(40): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#46 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php(167): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#47 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Middleware\\ValidatePostSize.php(27): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#48 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php(167): Illuminate\\Foundation\\Http\\Middleware\\ValidatePostSize->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#49 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance.php(86): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#50 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php(167): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#51 D:\\xampp\\htdocs\\recursion_test\\vendor\\fruitcake\\laravel-cors\\src\\HandleCors.php(38): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#52 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php(167): Fruitcake\\Cors\\HandleCors->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#53 D:\\xampp\\htdocs\\recursion_test\\vendor\\fideloper\\proxy\\src\\TrustProxies.php(57): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#54 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php(167): Fideloper\\Proxy\\TrustProxies->handle(Object(Illuminate\\Http\\Request), Object(Closure))
#55 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php(103): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))
#56 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Kernel.php(141): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))
#57 D:\\xampp\\htdocs\\recursion_test\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Kernel.php(110): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))
#58 D:\\xampp\\htdocs\\recursion_test\\public\\index.php(52): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))
#59 {main}
"} 

Versions

$ php please support:details

Statamic 3.3.18 Pro Laravel 8.60.0 PHP 8.0.19 jacksleight/bard-paragraph-style 1.2.3

Installation

Fresh statamic/statamic site via CLI

Antlers Parser

regex (default)

Additional details

No response

promanapeople avatar Jul 14 '22 01:07 promanapeople

If you're needing to get the parent entries, I'd suggest using the tree endpoint and avoid querying for the parent field.

/api/collections/recursion_test/tree?fields=title

jasonvarga avatar Jul 14 '22 15:07 jasonvarga

If you're needing to get the parent entries, I'd suggest using the tree endpoint and avoid querying for the parent field.

/api/collections/recursion_test/tree?fields=title

Actually I want all the posts that match certain filters, then looping over those posts I want to grab data about each post's parent. In this way in the client application, from a single API call I can formulate a list of posts and render a hint as to their "grouping" (eg the title of parent post in the collection)

It did work. EG here is a snippet from the client application, rendered after calling the 3.2.4 API) image

So in the blog tree, we have

  • Blog Home (root)
  • Essentials
    • 12 valuable vocational behaviours
  • Conversations
    • The great resignation
    • etc

promanapeople avatar Jul 15 '22 01:07 promanapeople

The bug also affects calls to a single entry identified by its ID

GET /api/collections/{collection}/entries/{id}

promanapeople avatar Jul 15 '22 02:07 promanapeople

Have same issue after upgrading from 3.2 to 3.3. Also, if remove parent inside vendor/laravel/framework/src/Illuminate/Http/JsonResponse.php like this for testing - no error image as i understand the problem:

  1. entry Page Services has parent page Home
  2. Home has relation field with page Contact
  3. Contact has parent Home as default parent

lotarbo avatar Sep 07 '22 17:09 lotarbo

@jasonvarga I create demo repo with fresh statamic install. https://github.com/lotarbo/statamic-api-issue

superuser
[email protected]
demodemo

Only three pages: Home, First, Second; I add relation field, then in home page I add second page as relation. Then try to get data via rest api for first page and I got the same error

lotarbo avatar Sep 07 '22 18:09 lotarbo

Still an error

Laravel: 9.35.1
PHP: 8.1.11
Statamic: 3.3.45 

lotarbo avatar Oct 17 '22 09:10 lotarbo

The issue appears to be that the "parent" entry should be "shallow" augmented, but isn't, which results in the infinite loop.

I have a workaround for you, which you can apply temporarily. I'll need some more time to investigate a proper fix.

For now, you can override the parent method in the augmented entry class. To do that you'll need to override a couple of classes.

// AppServiceProvider.php

public function register()
{
    $this->app->bind(
       \Statamic\Contracts\Entries\Entry::class, 
       \App\CustomEntry::class
    );
}
// app/CustomEntry.php

<?php

namespace App;

use Statamic\Contracts\Data\Augmented;
use Statamic\Entries\Entry;

class CustomEntry extends Entry
{
    public function newAugmentedInstance(): Augmented
    {
        return new CustomAugmentedEntry($this);
    }
}
// app/CustomAugmentedEntry.php

<?php

namespace App;

use Statamic\Entries\AugmentedEntry;

class CustomAugmentedEntry extends AugmentedEntry
{
    public function parent()
    {
        return optional($this->data->parent())->toShallowAugmentedCollection();
    }
}

Another solution would be to use GraphQL, which is in my opinion far superior. The recursion would not be an issue, and you'd be able to get whatever fields you need.

query MyQuery {
  entries(collection: "pages") {
    data {
      title
      url
      parent {
        title
      }
    }
  }
}


jasonvarga avatar Oct 17 '22 17:10 jasonvarga

@jasonvarga thank you, but project already done, and there are no addition time to change api to GraphQL(

lotarbo avatar Oct 20 '22 14:10 lotarbo