FOSRestBundle's serializer throws recursion error using inherited entities - symfony

I’m developing an application that inherits abstract classes. These abstract classes have their own mapping for the serializer as shown in the example bellow
Hezten\CoreBundle\Model\Enroled:
exclusion_policy: ALL
And the abstract class:
<?php
namespace Hezten\CoreBundle\Model;
abstract class Enroled implements EnroledInterface
{
protected $student;
protected $subject;
//Some functions...
}
The class that inherits the previous class looks as follows
<?php
namespace XXX\XXXBundle\Entity;
use JMS\Serializer\Annotation\SerializedName;
use JMS\Serializer\Annotation\ExclusionPolicy;
use JMS\Serializer\Annotation\Exclude;
use Doctrine\ORM\Mapping as ORM;
use Hezten\CoreBundle\Model\Enroled as BaseEnroled;
/**
* #ORM\Entity
* #ExclusionPolicy("NONE")
*/
class Enroled extends BaseEnroled
{
/** #ORM\Id
* #ORM\ManyToOne(targetEntity="XXX\XXXBundle\Entity\Student", inversedBy="enroled")
* #Exclude
*/
protected $student;
/** #ORM\Id
* #ORM\ManyToOne(targetEntity="XXX\XXXBundle\Entity\Subject", inversedBy="enroled")
* #Exclude
*/
protected $subject;
/** #ORM\Column(type="boolean") */
private $field0;
/** #ORM\Column(type="boolean")
*/
private $field1;
/** #ORM\Column(type="boolean") */
private $field2;
}
The error thrown says this:
Warning: json_encode() [<a href='function.json-encode'>function.json-encode</a>]: recursion detected in C:\xampp\htdocs\Project\vendor\jms\serializer\src\JMS\Serializer\JsonSerializationVisitor.php line 29
For sure, I'm doing something wrong as no entities are exposed, just three fields of "Enroled" entity according to the mappings but I have no clue. I spent a couple of days trying to figure out what's the mistake without success.
What is the proper way to do the mapping of inhertied properties?
Update
Code used to serialize JSON using FOSRestBundle:
$students = $this->get('hezten_core.manager.enroled')->findEnroledBySubject($subject);
$view = View::create()
->setStatusCode(200)
->setFormat('json')
->setData($students);
return $this->get('fos_rest.view_handler')->handle($view);

Finally the API is working... I had to override the metadata of the classes I was inheriting adding the following lines to the config.yml
jms_serializer:
metadata:
directories:
HeztenCoreBundle:
namespace_prefix: "Hezten\\CoreBundle"
path: "%kernel.root_dir%/serializer/HeztenCoreBundle"
In the path that is selected above I added one yml file for each Model setting exclusion policy to ALL:
Hezten\CoreBundle\Model\Enroled:
exclusion_policy: ALL
And I used annotations on the entities that were inheriting those models to expose required info.
I don't know if this is the best approach but works well for me

Related

API PLATFORM custom identifier in parent class/table

I have entities that make use of Inheritance Mapping Doctrine inheritance. I have a custom identifier that I generate with #ORM\PrePersist(), which is in a trait and this is used in the parent class.
I want to be able to update properties that the child class has, for this reason, I need to run endpoints on the child entity
When I run an item operation, api platform can't find the resource.
PATCH /api/childas/{hash}
NotFoundHttpException
Not Found
api platform, it doesn't recognize hash as identifier. Take the id as your identified, even if it is false and hash is true.
Trait to generate hashes with which I identify the resource
<?php
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiProperty;
use Doctrine\ORM\Mapping as ORM;
trait HashableTrait
{
/**
* #ORM\Column(type="string", length=255)
* #ApiProperty(identifier=true)
*/
private $hash;
public function getHash(): ?string
{
return $this->hash;
}
/**
* #ORM\PrePersist()
*/
public function setHash()
{
$this->hash = \sha1(\random_bytes(10));
}
}
Parent class, is the table where the hash will be stored
<?php
namespace App\Entity;
use App\Entity\HashableTrait;
/**
* #ORM\HasLifecycleCallbacks()
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="type", type="integer")
* #ORM\DiscriminatorMap({
* 1 = "App\Entity\ChildA",
* 2 = "App\Entity\ChildB"
* })
*/
class Parent
{
use HashableTrait;
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
* #ApiProperty(identifier=false)
*/
private $id;
public function getId(): ?int
{
return $this->id;
}
// Properties, setters, getters
}
Child class, on which I want to perform operations, such as updating some property that belongs to this class
<?php
namespace App\Entity;
class ChildA extends Parent
{
// Custom properties for ChildA
}
Config api platform operations for entity Child
App\Entity\ChildA:
collectionOperations:
post: ~
itemOperations:
post: ~
get: ~
patch: ~
delete: ~
I have thought about using data providers, but I keep getting the error.
The error was because both the hash property in the trait and the id property in the parent entity must be accessible from the entity to use.
Doctrine ORM uses reflection class to get information about attributes and their annotations. ReflectionClass::hasProperty obviously does not allow viewing private properties in the parent class.

Custom base entity in Symfony 4

I am using Symfony 4 and with Doctrine where I have entities which have the same common attributes such as createdWhen, editedWhen, ...
What i would like to do is this:
Defining a kind of base entity that holds these common attributes and implements the setter and getter. And many entities which inherit from that base entity. The database fields should all be defined in the table of the respective sub entity (no super table or the like should be created in the db).
Example:
/**
* #ORM\Entity(repositoryClass="App\Repository\BaseRepository")
*/
class Base
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=127, nullable=true)
*/
private $createdWhen;
// Getter and setter
...
}
/**
* #ORM\Entity(repositoryClass="App\Repository\PersonRepository")
*/
class Person extends Base
{
/**
* #ORM\Column(type="string", length=127, nullable=true)
*/
private $name;
// Getter and setter
...
}
/**
* #ORM\Entity(repositoryClass="App\Repository\CarRepository")
*/
class Car extends Base
{
/**
* #ORM\Column(type="string", length=127, nullable=true)
*/
private $brand;
// Setter and getter
...
}
This should create the tables "person" and "car" (each with id, created_when) but no table base.
I would still like to be able to use the bin/console make:migration for updating the database schema.
Is this kind of approach possible with Symfony 4? If yes how would I define the entities and what do I have to change in terms of configuration, etc.?
You are looking for entity inheritance
Rewrite your code like so
/** #MappedSuperclass */
class Base
{
...
}
In fact, this is a part of Doctrine, here is what an official documentation says
A mapped superclass is an abstract or concrete class that provides
persistent entity state and mapping information for its subclasses,
but which is not itself an entity. Typically, the purpose of such a
mapped superclass is to define state and mapping information that is
common to multiple entity classes.

Symfony 4. Methods from the repository of the abstract class are not available

I have an abstract class:
/**
* #ORM\Entity
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="type", type="string")
* #ORM\DiscriminatorMap({
* "LegalInsuranceProof" = "LegalInsuranceProofDocument",
* "SalesReceipt" = "SalesReceiptDocument"
* })
* #ORM\HasLifecycleCallbacks()
* #ORM\Table(name="document_abstract")
* #ORM\Entity(repositoryClass="App\Repository\DocumentRepository")
*/
abstract class AbstractDocument implements CreateFolderInterface
{
.
.
.
}
and the class, that extends this abstract class:
/**
* #ORM\Entity
* #ORM\HasLifecycleCallbacks()
* #ORM\Table(name="document_sales_receipt")
*/
class SalesReceiptDocument extends AbstractDocument
{
.
.
.
}
In the repo, I have defined the method getReviewListPaginator:
class DocumentRepository extends ServiceEntityRepository {
use PaginatorTrait;
public function __construct(RegistryInterface $registry) {
parent::__construct($registry, AbstractDocument::class);
}
public function getReviewListPaginator($limit, $offset) {
$this->assertQueryParameters($offset, $limit, "asc");
$qb = $this
->createQueryBuilder('d')
->select('PARTIAL d.{id, pageCount}')
->innerJoin('d.case', 'c')
->addSelect('PARTIAL c.{id}')
->setFirstResult($offset)
->setMaxResults($limit);
return new Paginator(
$qb->getQuery()->setHydrationMode(Query::HYDRATE_ARRAY),
true
);
}
}
If I do
$this->em->getRepository(AbstractDocument::class)->getReviewListPaginator(5,2);
the method getReviewListPaginator is called.
But If I do
$paginator = $this->em->getRepository(SalesReceiptDocument::class)->getReviewListPaginator(5,2);
I get en error message:
BadMethodCallException : Undefined method 'getReviewListPaginator'. The method name must start with either findBy, findOneBy or countBy!
But why? Should I define a repo for the SalesReceiptDocument entity, that extends the App\Repository\DocumentRepository?
I don't think the Repository are extended by default.
I think you need to do a SalesReceiptReporsitory that explicitly exteands your DocumentRepository
And add the repositoryClass options to your #Entity on SalesReceiptDocument.
Your #Entity annotations do not have the repository specified, change them to:
#Entity(repositoryClass="..namespace..\DocumentRepository")
See the #Entity documentation.
Edit 1:
I just noticed your AbstractDocument has duplicate #Entity annotation, you can just delete the empty one
Edit 2:
To select different document types you need separate repositories, to keep your code simple and non-repeating, you can use the $_entityName attribute of EntityRepository if you are extending it or have your own private attribute that would indicate the entity name for a repository and then use this entity name in the getReviewListPaginator to dynamically query the type of entity you want.
As far as I can tell, you cannot achieve this without having separate repositories for each type of document - even if each is empty, just extending the base repository and doing the parametrized query building as I described.

How to use repository method findByxx about field name with underline(_) in symfony

// This is my entity class object
use Doctrine\ORM\Mapping as ORM;
class PayOrder {
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string",unique=true)
*/
private $pay_no;
};
// I use it in my function, this is the Repository
use Doctrine\ORM\EntityManager;
use Exception;
use Psr\Container\ContainerInterface;
use Doctrine\ORM\Mapping;
class PayOrderRepository extends \Doctrine\ORM\EntityRepository {
public function get( PayOrder $payOrder ) {
$pay_no=$payOrder->getPayNo();
// It will occurs a exception, how to fix it, any one can help me?
return $this->findBypayno($pay_no);
}
};
Besides that, I can't find the document to fix the problem.
I want to use the field name pay_no, and I want to use the repository
findbyxxx, but I do not how to use it correctly.
Change your property name to $payNo.
Probably in your entity look like that;
//...
/**
* #ORM\Column(type="string", length=255)
*/
$pay_no
//....
Change it like that;
//...
/**
* #ORM\Column(name="pay_no", type="string", length=255)
*/
$payNo
//....
After that,
Remove old getter/setter for $pay_no
For Symfony2 run app/console doctrine:generate:entities
For Symfony4 run bin/console make:entity --regenerate
Goodluck. If you have any question or blocker please write me.
Or just use
$this->findBy(['pay_no' => $payOrder->getPayNo());
The findByX method are just intercepted method calls - see line up 177 to 179 that are transformed to findBy calls.

Doctrine Class Table Inheritance with Symfony2

I have 2 Symfony bundles. AdminBundle will always be installed. PageBundle may or may not be installed.
I want to define a base Entity called AdminModule (name, controller class, description, enabled), and also a PageModule which simply inherits from AdminModule ( the entities controller will implement a specific interface).
<?php
namespace AdminBundle\Entity;
/**
* Admin Component
*
* #ORM\Entity
* #ORM\Table(name="admin_module")
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="discr", type="string")
* #ORM\DiscriminatorMap({"page" = "\PageBundle\Entity\PageComponent"})
*/
class AdminModule
{
// private vars, getters, setters
}
?>
<?php
namespace PageBundle\Entity;
use AdminBundle\Entity\AdminModule;
/**
* Page Component
*
* #ORM\Entity
* #ORM\Table(name="page_module")
*/
class PageModule extends AdminModule
{
//
}
?>
The issue I have, I think, is that the AdminModule annotation #ORM\DiscriminatorMap({"page" = "\PageBundle\Entity\PageModule"}) requires definition on the AdminBundle - but the PageBundle may not be installed.
I believe must have the wrong type of inheritance structure (?) however I am not clear on what alternative approaches I can take? Thanks for any help or direction :)
you can't do what you're trying to with table inheritance mappings,
because you have to write annotations in the parent class, so the parent class itself ends up being coupled with his children.
what you could use is a mapped superclass (#MappedSuperclass) to extend the actual parent entities from.
all your common properties should then go into the mapped superclass, using its children as actual entities to define different inheritance mappings and associations (association mappings in mapped superclasses are very limited).
so in your specific case you could have such a structure:
/**
* I'm not an actual Entity!
*
* #MappedSuperClass */
Class ModuleSuperClass {}
/**
* I don't have children
*
* #ORM\Entity */
Class BaseModule extends ModuleSuperClass {}
/**
* I have children
*
* #ORM\Entity
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="discr", type="string")
* #ORM\DiscriminatorMap({"page" = "Page"})
*/
Class AdminModule extends ModuleSuperClass {}
/**
* I'm just a child
*
* #ORM\Entity
*/
Class PageModule extends AdminModule {}
your mileage may of course vary, i.e. I would rather have a BaseModule class without children, and then a BaseModule in an entirely different namespace to extend both AdminModule and PageModule from.

Resources