Turn off pluralisation for an endpoint in APi Platform - php

In a Symfony 5 project we're using the APi Platform to generate a REST API.
One of the entity classes is called FarmMetadata.
<?php
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\ORM\Mapping as ORM;
/**
* #ApiResource()
* #ORM\Table(... some settings ...)
* #ORM\Entity
*/
class FarmMetadata
{
// properties and methods
}
When I run php bin/console debug:router it shows the following routes for this resource:
api_farm_metadatas_get_collection GET ANY ANY /api/farm_metadatas.{_format}
api_farm_metadatas_post_collection POST ANY ANY /api/farm_metadatas.{_format}
api_farm_metadatas_get_item GET ANY ANY /api/farm_metadatas/{id}.{_format}
api_farm_metadatas_delete_item DELETE ANY ANY /api/farm_metadatas/{id}.{_format}
api_farm_metadatas_put_item PUT ANY ANY /api/farm_metadatas/{id}.{_format}
api_farm_metadatas_patch_item PATCH ANY ANY /api/farm_metadatas/{id}.{_format}
However the word "metadata" is already plural. There's no such thing as metadatas. How can I turn off the pluralisation for this endpoint?
I tried using shortName:
* #ApiResource(
* shortName="FarmMetadata" // also "farm_metadata"
* )
but it doesn't change the output.
If I use:
* #ApiResource(
* shortName="Metadata"
* )
then the route names and paths are changed:
api_metadata_get_collection GET ANY ANY /api/metadata.{_format}
api_metadata_post_collection POST ANY ANY /api/metadata.{_format}
api_metadata_get_item GET ANY ANY /api/metadata/{id}.{_format}
api_metadata_delete_item DELETE ANY ANY /api/metadata/{id}.{_format}
api_metadata_put_item PUT ANY ANY /api/metadata/{id}.{_format}
api_metadata_patch_item PATCH ANY ANY /api/metadata/{id}.{_format}
but that's not what I want.
I know that I can declare a path for every operation, but that would hurt the DRY principle.
How can I achieve the desired behaviour?

You could use "path" option on each operation.
Cf https://api-platform.com/docs/core/operations/#configuring-operations
For example
* shortName="Metadata",
* itemOperations={
* "get"={
* "path"="/metadata/{id}"

I don't think this is possible by configuration: these routes are built in the private method ApiPlatform\Core\Bridge\Symfony\Routing\ApiLoader::addRoute (at least in v2.6 which I'm using), and this uses a static call to a pluralizer - so: decorating the ApiLoader is not easily possible (as the addRoute method is private), and exchanging the ways of generating the route is not possible (due to the usage of a static method call).
Looks like you need to open a feature request ticket in their bug tracker...

you can do easily as you want
api_platform:
...
path_segment_name_generator: App\InfraStructure\ApiPlatform\Core\SingularPathSegmentNameGenerator
create SingularPathSegmentNameGenerator
<?php
declare(strict_types=1);
namespace App\InfraStructure\ApiPlatform\Core;
use ApiPlatform\Core\Operation\PathSegmentNameGeneratorInterface;
use ApiPlatform\Core\Util\Inflector;
final class SingularPathSegmentNameGenerator implements PathSegmentNameGeneratorInterface
{
public function getSegmentName(string $name, bool $collection = true): string
{
return Inflector::tableize($name);
}
}

Related

Symfony router {_controller} param

i'm new to Symfony. I'm trying to create dynamic universal route that will pick required controller based on part of url. I've found in docs that there is special param {_controller} that can be used in route pattern, but could not find any examples of usage.
// config/routes.yaml
api:
path: /api/{_controller}
So for example for route /api/product i expect ProductController to be initiated.
But as a result i get error "The controller for URI "/api/product" is not callable: Controller "product" does neither exist as service nor as class."
Can somebody please help me understanding how {_controller] param works? Or maybe there is a better way for specifying universal route that can dynamically chose controller without listing controller names in routes.yaml.
Thanks in advance
This isn't really the cleanest way to do what I think you're trying to do. If I am correct in assuming you want to have a /api/product/ point to methods in your product controller, then something like this is more "symfonyish"
// src/Controller/ProductController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* #Route("/api/product", name="product_")
*/
class ProductController extends AbstractController
{
/**
* #Route("/", name="index")
* --Resolves to /api/product/
*/
public function index(): Response
{
// ...
}
/**
* #Route("/list", name="list")
* --Resolves to /api/product/list
*/
public function list(): Response
{
// ...
}
/**
* #Route("/{productID}", name="get")
* --Resolves to /api/product/{productID}
*/
public function get(string $productID): Response
{
// $productID contains the string from the url
// ...
}
}
Note that this really just scratches the surface of Symfony routing. You can also specify things like methods={"POST"} on the routing directive; so you could have the same path do different things depending on the type of request (e.g. you could have a route /product/{productID} that GETs the product on that request but updates the product on a PATCH request.
Regardless, the takeaway here is that it is unwieldy to have all of your routes defined in routes.yml rather you should define your routes as directives in the controller itself.

Symfony 4 Custom annotation problem #ORM\Entity does not exist

as part of the development of my CMS that I publish in a while .. I am facing a problem.
error :
[Semantical Error] The annotation "#Doctrine\ORM\Mapping\Entity" in class ScyLabs\GiftCodeBundle\Entity\GiftCode does not exist, or could not be auto-loaded.
I explain to you ,
Basically, in the project, everything is Overridable, it is already the case, with configurations in the file services.yaml.
For obvious reasons of simplicity, and an immediate need, to allow me to create a second bundle inheriting from it. I told myself that doing my "Override" or saying to the project: "Hello here I am, I am a class uses me" is very convenient with annotations (and much clearer).
So, I create a custom annotation (So far so good ..) That you find here ..
<?php
/**
* Created by PhpStorm.
* User: alex
* Date: 04/11/2019
* Time: 14:25
*/
namespace ScyLabs\NeptuneBundle\Annotation\ScyLabsNeptune;
/**
* #Annotation
* #Target({"CLASS"})
* #Attributes({
* #Attribute("key",type="string"),
* #Attribute("classNameSpace",type="string"),
* })
*/
class Override
{
/**
* #var string
*/
public $key;
/**
* #var string
*/
public $classNameSpace;
public function __construct(array $opts) {
$this->key = $opts['value'];
$this->classNameSpace = $opts['class'];
}
}
Well, my annotation was in place, I will now put it in an entity, .. As here
<?php
/**
* Created by PhpStorm.
* User: alex
* Date: 05/11/2019
* Time: 10:20
*/
namespace ScyLabs\GiftCodeBundle\Entity;
use ScyLabs\NeptuneBundle\Annotation\ScyLabsNeptune;
use Doctrine\ORM\Mapping as ORM;
/**
* #ScyLabsNeptune\Override("gift",class="ScyLabs\GiftCodeBundle\Entity\GiftCode")
* #ORM\Entity()
*/
class GiftCode
{
}
Why do that ? And in fact, everything is automated in the neptune, except special case, it will automatically generate all the URLs necessary for the proper functioning of an entity (ADD / EDIT / DELETE / LIST) ... And for this, it must indicate to the project that the entity exists, and that it must be part of this system.
So, until now I use a very complete configuration in services.yaml, in which I fill a table keys => value, corresponding to "key" => "Namespace"
In my case: "gift" => "ScyLabs \ GiftCodeBundle \ Entity \ GiftCode"
In short, suddenly, for override, I do a treatment in a compilation step
<?php
/**
* Created by PhpStorm.
* User: alex
* Date: 01/08/2018
* Time: 09:46
*/
namespace ScyLabs\NeptuneBundle;
class ScyLabsNeptuneBundle extends Bundle
{
public function getContainerExtension()
{
// Compilation de l'extension
return new ScyLabsNeptuneExtension();
}
}
And in this extension, I have this piece of code that makes everything
$bundles = require $projectDir.'/config/bundles.php';
foreach ($bundles as $bundle => $env){
if($bundle === ScyLabsNeptuneBundle::class)
continue;
if(method_exists(new $bundle,'getParent') && (new $bundle)->getParent() === ScyLabsNeptuneBundle::class){
$reader = new AnnotationReader();
$reflClass = new \ReflectionClass(GiftCode::class);
$classAnotations = $reader->getClassAnnotation($reflClass,"Override");
foreach ($classAnotations as $classAnotation){
if($classAnotation instanceof Override && class_exists($classAnotation->classNameSpace)){
$config['override'][$classAnotation->key] = $classAnotation->classNameSpace; }
}
}
}
From what I suspect after a lot of research, at the compilation stage of my extension, #ORM \ Entity, and or / / Autowire, it seems not compiled yet.
The problem is that suddenly, when I get my personal annotation (Override), I can not recover #ORM \ Entity, and I can not necessarily remove it because it would not work anymore as an entity.
Why do that here? Because behind I have another step of comoilation (A CompilationPass)
$container->addCompilerPass(new ResolveDoctrineTargetEntitiesPass(),PassConfig::TYPE_BEFORE_OPTIMIZATION,1000);
Who, redefined the Entities that doctrine will call in relation to the painting that I send to him (you know, the one I defined just before).
With this I give the possibility of override entities with an identical name.
What to do ?? .. I confess that I can not do more ...
Thanks in advance friends;)
By default, the annotation reader does not use the same autoloader as classes.
You need to tell him how to load the annotation class like that :
AnnotationRegistry::registerUniqueLoader('class_exists');
For more explanation, you can look at the doc https://www.doctrine-project.org/projects/doctrine-annotations/en/1.6/annotations.html#registering-annotations
thanks for you response.
But it don't work and this fonction is deprecated and removed to Annotations 2.0.
BUT , when i try i found a resolution.
When i follow your link and try the code in the page , i try this function
AnnotationRegistry#registerFile($file)
For get #ORM\Entity file path , i use
new \ReflectionClass(ORM\Entity::class);
And , this work.
I deleted AnnotationRegistry#registerFile($file) function , and this work.
Thanks you for help ;)
You'r the best

Doctrine attributes namespace

trying to understand what is the Doctrine. I using PHPStorm 2016.3.2 and plugin "PHP Annotations".
When i'm create the Model i'm trying to use annotations like this:
use \Doctrine\ORM\Mapping as ORM;
/**
* Class Region
* #package models
* #ORM\Entity()
* #ORM\Table(name="regions")
*/
class Region { ... }
In annotations i'm using not #Entity, i'm using #ORM\Entity() because IDE understanding what is that and making tips for me. But on this way Doctrine didnt see my classes. How i can to resolve this problem? Thanks.
I came across the same issue while creating the Doctrine configuration through the
Setup::createAnnotationMetadataConfiguration()
method. When you set its fifth parameter $useSimpleAnnotationReader to false you can use the name-spaced syntax the PhpStorm extension expects.

In the Eclipse PDT, is it possible to configure content assist to look for alternative tags to suggest PHP types?

I'm working on a project that uses the Lithium (http://li3.me/) framework and they document their classes like this:
class Controller extends \lithium\core\Object {
/**
* Contains an instance of the `Request` object with all the details of the HTTP request that
* was dispatched to the controller object. Any parameters captured in routing, such as
* controller or action name are accessible as properties of this object, i.e.
* `$this->request->controller` or `$this->request->action`.
*
* #see lithium\action\Request
* #var object
*/
public $request = null;
I've always used fully qualified class names in the #var and Eclipse seems to do a good job with that for generating content assist. However they seem to document class names using #see tags instead, and content assist is not available. Is there a way to configure PDT to use the information in the #see tag as a class name for the purposes of content assist?
It's not possible without own plugin. #see tag should be used for links only.

Symfony2 __toString() generation

Does Symfony2 have an automatic __toString() generation based on the entity fields, or an annotation to say that the __toString() should be generated, similar to Java Roo?
I cannot find such a feature under the annotations reference, and the consensus among the Google Group seems to side with defining __toString() on the object.
If you use an IDE such as Net Beans, a simple CTRL+SPACE hotkey and click will automatically generate the __toString() for you, you'd simply need to fill out the refence to whichever attribute you want to use to represent the object.
Furthermore, you could take that one step further and define an Entity template (which is what I do in Net Beans). Something like this could save you some time, keeping in mind Doctrine2 is my ORM in this example, and I use the annotations method of defining my entities:
<?php
namespace Foo\BarBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
//use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity
*/
class ${name}
{
/**
* #ORM\Id #ORM\Column(type="integer")
* #ORM\GeneratedValue
*/
protected $id;
public function __toString()
{
//return $this->get();
}
}
This automatically fills out the class name and has ArrayCollection commented out (so I can easily add that in if the entity requires it). This would leave you with simply needing to fill in the rest of whatever method you'd like to use for __toString();
${name} is a template variable in NetBeans.

Categories