core: Error after disabling docs : Unable to generate a URL for the named route \"api_doc\" as such route does not exist.

This is my second project with Api Platform, the API is close to be ready for release. I switched the .env to prod, and disabled the docs, since I do not need or want to share it publicly.

# config/packages/api_platform.yaml
api_platform:
    enable_docs: false
    show_webby: false
    mapping:
        paths: ['%kernel.project_dir%/src/Entity']
    patch_formats:
        json: ['application/merge-patch+json']
    swagger:
        versions: [3]

This makes some resources unnaccessible with GET methods, as I get the 500 error : Unable to generate a URL for the named route \"api_doc\" as such route does not exist.

This only seems to happen using json-ld format, for a few resources, with the get collection operation. Everything works fine with an Accept json header or .json at the end, and the get item operation, but I need the json-ld format for the bundled pagination that’s very handy.

I’ve tried to remove unnecessary annotations, filters, groups, update my packages and symfony, but I still have the same issue.

Here’s one of the non-working resources, it’s very simple and it doesn’t use a DataProvider :

<?php
// src/Entity/Score.php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use App\Repository\ScoreRepository;
use ApiPlatform\Core\Annotation\ApiFilter;
use ApiPlatform\Core\Annotation\ApiResource;
use Symfony\Component\Serializer\Annotation\Groups;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;

/**
 * @ApiResource(
 *      attributes={
 *          "order"={"delta": "ASC"},
 *          "pagination_client_items_per_page"=true,
 *          "maximum_items_per_page"=50
 *      },
 *      collectionOperations={
 *         "get"={
 *             "normalization_context"={"groups"={"score:read"}}
 *          }
 *      },
 *      itemOperations={
 *          "get"={
 *             "normalization_context"={"groups"={"score:read"}},
 *              "security"="is_granted('ROLE_USER')"
 *          },
 *         "delete"={"security"="is_granted('ROLE_ADMIN')"}
 *      })
 * @ApiFilter(SearchFilter::class, properties={"user": "exact", "user.address.country": "exact"})
 * @ORM\Entity(repositoryClass=ScoreRepository::class)
 */
class Score
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     * @Groups({"enter:score", "score:read"})
     */
    private $id;

    /**
     * @ORM\ManyToOne(targetEntity=User::class, inversedBy="scores")
     * @ORM\JoinColumn(nullable=false)
     * @Groups({"enter:score", "score:read"})
     */
    private $user;

    /**
     * @ORM\ManyToOne(targetEntity=Challenge::class, inversedBy="scores")
     * @ORM\JoinColumn(nullable=true)
     * @Groups({"enter:score", "score:read"})
     * @ApiFilter(SearchFilter::class, strategy="exact")
     */
    private $challenge;

    /**
     * @ORM\ManyToOne(targetEntity=Course::class)
     * @ORM\JoinColumn(nullable=true)
     * @Groups({"enter:score", "score:read"})
     * @ApiFilter(SearchFilter::class, strategy="exact")
     */
    private $course;

    /**
     * @ORM\Column(type="string", length=255, nullable=true)
     * @Groups({"enter:score", "score:read"})
     */
    private $type;

    /**
     * @ORM\Column(type="float")
     * @Groups({"enter:score", "score:read"})
     */
    private $delta;

    /**
     * @ORM\ManyToOne(targetEntity=Copilot::class)
     * @Groups({"enter:score", "score:read"})
     */
    private $copilot;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getUser(): ?User
    {
        return $this->user;
    }

    public function setUser(?User $user): self
    {
        $this->user = $user;

        return $this;
    }

    public function getChallenge(): ?Challenge
    {
        return $this->challenge;
    }

    public function setChallenge(?Challenge $challenge): self
    {
        $this->challenge = $challenge;

        return $this;
    }

    public function getCourse(): ?Course
    {
        return $this->course;
    }

    public function setCourse(?Course $course): self
    {
        $this->course = $course;

        return $this;
    }

    public function getType(): ?string
    {
        return $this->type;
    }

    public function setType(?string $type): self
    {
        $this->type = $type;

        return $this;
    }

    public function getDelta(): ?float
    {
        return $this->delta;
    }

    public function setDelta(float $delta): self
    {
        $this->delta = $delta;

        return $this;
    }

    public function getCopilot(): ?Copilot
    {
        return $this->copilot;
    }

    public function setCopilot(?Copilot $copilot): self
    {
        $this->copilot = $copilot;

        return $this;
    }
}

Does any one encountered the same issue ? And how may I fix it ? A little help would be much appreciated as almost everything is working

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 4
  • Comments: 15 (9 by maintainers)

Commits related to this issue

Most upvoted comments

@soyuka What if we want jsonld AND not show our documentation ?

A possible workaround: keep the doc but add a rule to block the access:

security:
    # ...

    access_control:
        - { path: '^/api$', roles: ROLE_SUPER_ADMIN }

See also api-platform/api-platform#1504

Thank you I didn’t find similar issues, but this looks like the best option ! I’ll try it a bit later

This is very annoying. There’re 4 locations in the code with code like $this->urlGenerator->generate('api_doc', ...) - without checking if the feature is actually enabled.

For me it fails in ContextBuilder.php in the method getBaseContext. Then it calls it again in AddLinkHeaderListener.php in the method onKernelResponse.

Any idea how to fix this?

It’s confusing that the enable_docs, enable the api_doc route but it does not enable the doc !

It looks like the value of enable_docs is never checked: https://github.com/api-platform/core/search?q=enable_docs

Another workaround may be to define api_doc to replace the original route with a blank page.