I'm trying to read annotations with Symfony4 but looks like something is not working!
The class I'm trying to read from:
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity(repositoryClass="App\Repository\OAuthClientRepository")
*/
class OAuthClient {
{
/**
* #Assert\NotBlank()
* #ORM\Column(type="string")
*/
protected $name;
}
The code I'm using to read the annotations:
<?php
namespace App\Test;
use Doctrine\Common\Annotations\SimpleAnnotationReader as DocReader;
/**
* Helper AnnotationReader
*/
class AnnotationReader
{
/**
* #param $class
* #return null|\StdClass
*/
public static function getClass($class)
{
$reader = new DocReader;
$reflector = new \ReflectionClass($class);
return $reader->getClassAnnotations($reflector);
}
/**
* #param $class
* #param $property
* #return array
*/
public static function getProperty($class, $property)
{
$reader = new DocReader;
$reflector = new \ReflectionProperty($class, $property);
return $reader->getPropertyAnnotations($reflector);
}
/**
* #param $class
* #param $method
* #return array
*/
public static function getMethod($class, $method)
{
$reader = new DocReader;
$reflector = new \ReflectionMethod($class, $method);
return $reader->getMethodAnnotations($reflector);
}
}
I get empty arrays when I call:
App\Test\AnnotationReader::getClass(App\Entity\OAuthClient::class);
App\Test\AnnotationReader::getProperty(App\Entity\OAuthClient::class, 'name');
What am I doing wrong?
What is the best way to read annotation?
I'm looking to read the validations used on a class property.
Thank you for your help!
change
use Doctrine\Common\Annotations\SimpleAnnotationReader as DocReader;
to
use Doctrine\Common\Annotations\AnnotationReader as DocReader;
and it works.
You may have to call the addNamespace() method on the SimpleAnnotationReader instance.
For instance, for ORM annotations:
$reader->addNamespace('Doctrine\ORM\Mapping');
And for validation annotations:
$reader->addNamespace('Symfony\Component\Validator\Constraints');
See:
SimpleAnnotationReader API: https://www.doctrine-project.org/api/annotations/latest/Doctrine/Annotations/SimpleAnnotationReader.html
SimpleAnnotationReader examples: https://github.com/doctrine/doctrine2/blob/462173ad71ae63cd9877e1e642f7968ed1f9971b/lib/Doctrine/ORM/Configuration.php#L140-L141
Related
in zend framework you can easily return $this->notFoundAction(); to return 404 (not found). The 'not_found_template' application config key is used to render the content.
We want to do the same but with a different status code 410 (gone). I cant figure out how to do this. I tried to return my own response but i cant set my view template.
What would be the prefered way to do this?
I created an Controller Plugin for this.
<?php
namespace Application\Controller\Plugin;
use Zend\Http\Response;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\Mvc\Controller\Plugin\AbstractPlugin;
use Zend\View\Model\ViewModel;
use Zend\View\Renderer\PhpRenderer;
/**
* Class GoneAction
*
* #package Application\Controller\Plugin
*/
class GoneAction extends AbstractPlugin
{
/** #var PhpRenderer $phpRenderer */
private $phpRenderer;
/**
* GoneAction constructor.
*
* #param PhpRenderer $phpRenderer
*/
public function __construct(PhpRenderer $phpRenderer)
{
$this->phpRenderer = $phpRenderer;
}
/**
* #return ViewModel
*/
public function __invoke(): ViewModel
{
/** #var AbstractActionController $controller */
$controller = $this->getController();
/** #var Response $response */
$response = $controller->getResponse();
$response->setStatusCode(Response::STATUS_CODE_410);
$viewModel = new ViewModel();
$viewModel->setTemplate('layout/gone');
return $viewModel;
}
}
Im trying to change an old arraycolection of $linhas for a new one by using the method
setLinhas(Arraycollection $linhas)
but what happens when it does the changes is that internally he creates a new object with the new lines and dont update the old object with the new lines. It creates a new instance with the same values as the old object. It was suppose to update the same object and not create a new one!
Entity's Property :
/**
* #var ArrayCollection
*
* #ORM\OneToMany(targetEntity="AppBundle\Entity\LinhasPrecos", mappedBy="preco",orphanRemoval=true,cascade={"persist","merge"})
*/
protected $linhas;
/**
* #param $linhas
*/
public function setLinhas($linhas)
{
$this->linhas = new ArrayCollection($linhas);
}
In the service:
$oldObject->setLinhas($newObectWithNewLinhas->getLinhas());
$this->em->persist($oldObject);
but if I do the change manually it will work:
$oldLinhas = $oldObject->getLinhas()->getValues();
foreach($oldLinhas as $oldLinha)
{
$oldObject->removeLinha($oldLinha);
}
$linhaToCopy = $newObectWithNewLinhas->getLinhas()->getValues();
foreach($linhasCopyNew as $linhaCopyNew)
{
$oldObject->addLinha($linhaCopyNew);
}
thanks in advance!
You are doing it wrong!
use this constructor and setter instead:
Preco
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
*/
class Preco
{
//...
/**
* #var Collection
*
* #ORM\OneToMany(targetEntity="AppBundle\Entity\LinhasPrecos", mappedBy="preco", orphanRemoval=true, cascade={"persist","merge"})
*/
protected $linhas;
//...
public function __construct()
{
$this->linhas = new ArrayCollection();
}
public function setLinhas($linhas)
{
$this->linhas = $linhas;
}
}
Notice
You should pass a doctrine collection into setLinhas.
This way you are totally replacing an old collection, with the new collection (and not adding an element to the old collection).
I'm trying to get working 4 entities in Symfony 3 with Doctrine 2 but I'm stuck on a circular reference exception when I want to serialize an Account entity for example:
A circular reference has been detected (configured limit: 1).
I chose bi-directional relations in my entities and schema is like this:
- Account [1] ---- [0..*] AccountSheet
- AccountSheet [1] ---- [0..*] Operation
- Operation [0..*] ---- [1] Category
Here are entities (with some cleanings for clarity):
src\AppBundle\Entity\Account.php
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use AppBundle\Entity\AbstractGenericEntity;
/**
* #ORM\Entity()
* #ORM\Table(name="accounts",
* uniqueConstraints={#ORM\UniqueConstraint(name="accounts_name_unique",columns={"name"})})
*/
class Account extends AbstractGenericEntity{
/**
* #ORM\OneToMany(targetEntity="AccountSheet", mappedBy="account")
* #var AccountSheet[]
*/
protected $accountSheets;
public function __construct($name = null, $description = null){
$this->accountSheets = new ArrayCollection();
$this->name = $name;
$this->description = $description;
}
}
src\AppBundle\Entity\AccountSheet.php
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use AppBundle\Entity\AbstractGenericEntity;
/**
* #ORM\Entity()
* #ORM\Table(name="accounts_sheets",
* uniqueConstraints={#ORM\UniqueConstraint(name="accountsheet_account_unique", columns={"name", "account_id"})})
* #ORM\HasLifecycleCallbacks
*/
class AccountSheet extends AbstractGenericEntity{
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Account", inversedBy="accountSheets")
* #var Account
*/
protected $account;
/**
* #ORM\OneToMany(targetEntity="Operation", mappedBy="accountSheet")
* #var Operation[]
*/
protected $operations;
public function __construct($name = null){
$this->operations = new ArrayCollection();
$this->name = $name;
}
}
src\AppBundle\Entity\Operation.php
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use AppBundle\Entity\AbstractGenericEntity;
/**
* #ORM\Entity()
* #ORM\Table(name="operations")
*/
class Operation extends AbstractGenericEntity{
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\AccountSheet", inversedBy="operations")
* #ORM\JoinColumn(nullable=false)
* #var AccountSheet
*/
protected $accountSheet;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Category", inversedBy="operations")
* #var Category
*/
protected $category;
public function __construct($type = null, $label = null, $montant = null, $comment = null){
$this->label = $label;
$this->type = $type;
$this->comment = $comment;
$this->montant = $montant;
}
}
src\AppBundle\Entity\Category.php
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use AppBundle\Entity\AbstractGenericEntity;
/**
* #ORM\Entity()
* #ORM\Table(name="categories")
*/
class Category extends AbstractGenericEntity{
/**
* #ORM\Column(type="string")
*/
protected $label;
/**
* #ORM\Column(type="string")
*/
protected $description;
/**
* #ORM\OneToMany(targetEntity="Operation", mappedBy="category")
* #var Operation[]
*/
protected $operations;
public function __construct($name = null){
$this->operations = new ArrayCollection();
$this->name = $name;
}
}
I guess it's on the Operation entity, where AccountSheet is referenced again. The bi-directional on operation is not really needed.
How could I rearrange this?
Thanks!
From the official documentation :
Circular references are common when dealing with entity relations
To avoid infinite loops, GetSetMethodNormalizer throws a CircularReferenceException when such a case is encountered:
$member = new Member();
$member->setName('Kévin');
$org = new Organization();
$org->setName('Les-Tilleuls.coop');
$org->setMembers(array($member));
$member->setOrganization($org);
echo $serializer->serialize($org, 'json'); // Throws a CircularReferenceException
So, from this point, you have 3 solutions to get rid of this issue :
Set a circular reference handler :
Instead of throwing an exception, circular references can also be handled by custom callables. This is especially useful when serializing entities having unique identifiers:
$encoder = new JsonEncoder();
$normalizer = new ObjectNormalizer();
$normalizer->setCircularReferenceHandler(function ($object) {
return $object->getName();
});
$serializer = new Serializer(array($normalizer), array($encoder));
var_dump($serializer->serialize($org, 'json'));
// {"name":"Les-Tilleuls.coop","members":[{"name":"K\u00e9vin", organization: "Les-Tilleuls.coop"}]}
Set ignored attributes (not my preferred solution) :
in your case :
$encoder = new JsonEncoder();
$normalizer = new ObjectNormalizer();
normalizer->setIgnoredAttributes(array("account", "accountSheet", "category", "operation"));
$serializer = new Serializer(array($normalizer), array($encoder));
var_dump($serializer->serialize($org, 'json'));
Use group attributes (my preferred solution) :
This method is similar to setting ignored attributes because you will chose which attribute you want to serialize by adding the group annotation on it and the rest will be ignored for recursivity during normalization process.
Using Serialization Groups Annotations
Attributes Groups
In your case with the Account entity for example do this on the account side :
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use AppBundle\Entity\AbstractGenericEntity;
use Symfony\Component\Serializer\Annotation\Groups;
/**
* #ORM\Entity()
* #ORM\Table(name="accounts",
* uniqueConstraints={#ORM\UniqueConstraint(name="accounts_name_unique",columns={"name"})})
*/
class Account extends AbstractGenericEntity{
/**
* #ORM\OneToMany(targetEntity="AccountSheet", mappedBy="account")
* #var AccountSheet[]
* #Groups({"account"})
*/
protected $accountSheets;
public function __construct($name = null, $description = null){
$this->accountSheets = new ArrayCollection();
$this->name = $name;
$this->description = $description;
}
}
Then do not put this group annotation on the $account field in the AccountSheet entity to get rid of the circular reference issue.
Finally you serialize your Account :
$encoder = new JsonEncoder();
$normalizer = new ObjectNormalizer();
$serializer = new Serializer(array($normalizer), array($encoder));
var_dump($serializer->serialize($account, 'json', array('groups' => array('account')) ));
$jsonContent = $serializer->serialize($yourObject, 'json', [
'circular_reference_handler' => function ($object) {
return $object->getId();
}
]);
Above code works for me to fix circular reference exception. (Symfony >=4.2)
I'm making my first small app in symfony, a simple blog.
Now I have been using the documentation for both symfony and doctrine and want to preform a simple, beginner task: display a json encoded simple table
Yet somehow I cant seem to get along with doctrine.
Here is my data (apart form the view which does nothing but display the value):
//AppBundle/Controller/DefaultController.php
<?php
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use AppBundle\Entity\Post;
use Doctrine\Common\Persistence;
class DefaultController extends Controller
{
/**
* #Route("/", name="homepage")
*/
public function indexAction(Request $request)
{
$database = new Post();
$output = $database->findAll();
return $this->render('default/index.html.twig', [
'base_dir' => realpath($this->getParameter('kernel.root_dir').'/..').DIRECTORY_SEPARATOR,
'x' => json_encode($output)
]);
}
}
<?php
//AppBundle/Entity/Post.php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\EntityRepository;
/**
* #ORM\Entity
* #ORM\Table(name="sqltest")
*/
class Post extends EntityRepository
{
//The post for now uses data from a temponary test table
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=100)
*/
private $name;
/**
* #ORM\Column(type="integer", scale=2)
*/
private $number;
/**
* Get id
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
* #param string $name
* #return Post
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set number
* #param integer $number
* #return Post
*/
public function setNumber($number)
{
$this->number = $number;
return $this;
}
/**
* Get number
* #return integer
*/
public function getNumber()
{
return $this->number;
}
}
Problem is when I try to display the website i get this exception
Warning: Missing argument 1 for
Doctrine\ORM\EntityRepository::__construct(), called in
C:\Users\Alan\Desktop\symf-blog\src\AppBundle\Controller\DefaultController.php
on line 19 and defined
Problematic line being the one with $database = new Post();
I am very new in this and am aware that the response is very simple and I just don't see it. When answering please provide and explanation which even a dead rabbit could understand.
Pretty thanks for your patience.
PS: Also an explanation about what the $em variable I've seen so much about is for and from where do I get it would be nice
If you're want a repository class for custom DB functions then this is the right way to do it:
namespace AppBundle\Entity;
use Doctrine\ORM\EntityRepository;
class PostRepository extends EntityRepository
{
public function findAll()
{
return $this->findBy(array(), array('id' => 'DESC', 'createdAt' => 'DESC'));
}
....
}
Then in your controller:
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository("AppBundle:Post")->findAll();
Remove the annotations (them belongs to the entity). Also pay attention to what #james_bond told you. Try that!
As stated in the documentation, you access custom repository classes through doctrine entity manager.
$em = $this->getDoctrine()->getManager();
$posts = $em->getRepository('YourBundle:Post')->findAll();
Also you're mixing your entity definition with your repository definition, which is not a good idea.
Please refer to the doctrine documentation in symfony for proper usage.
I'm using the BeSimple SoapBundle to create a SOAP server but i'm having troubles with the following piece of code:
namespace AppBundle\Entity;
use BeSimple\SoapBundle\ServiceDefinition\Annotation as Soap;
/**
* #Soap\Alias("Item")
*/
class Item
{
/**
* #Soap\ComplexType("AppBundle\Entity\Item[]")
*/
protected $items;
/**
* #Soap\ComplexType("string")
*/
protected $name;
What i need to get is a tree of complextype items but i'm getting a circular reference error when using the annotation #Soap\ComplexType("AppBundle\Entity\Item[]").
Any idea of how can i deal with this situation?
I finally found a workaround for this issue. I think that it shouldn't be necessary but is the best and only way i found.
namespace AppBundle\Entity;
use BeSimple\SoapBundle\ServiceDefinition\Annotation as Soap;
/**
* #Soap\Alias("ComplexItem")
*/
class ComplexItem extends Item
{
/**
* #Soap\ComplexType("AppBundle\Entity\Item")
*/
protected $item;
public function setItem($item)
{
$this->item = $item;
}
namespace AppBundle\Entity;
use BeSimple\SoapBundle\ServiceDefinition\Annotation as Soap;
class Item
{
/**
* #Soap\ComplexType("string")
*/
protected $name;
Controller usage example:
/**
* #Soap\Method("getItem")
* #Soap\Result(phpType = "AppBundle\Entity\ComplexItem")
*/
public function getItem()
{
$item = new ComplexItem();
$complexItem = new ComplexItem();
$complexItem->setItem($item);
return $complexItem;
}
Hope it helps someone. If you know a better way to solve this, please, let me know.