retrieve attribute from object in entity doctrine - symfony

So I have this function :
MyProject\Bundle\Entity\Audio;
/**
*
* #return string
*/
public function getStudioName()
{
return $this->getStudio()->getNom();
}
which is supposed to retrieve the attribute nom from the object Studio.
They are defined like this :
/**
* #var \MyProject\Bundle\Entity\Device
*/
private $studio;
...
/**
* Get studio
*
* #return \MyProject\Bundle\Entity\Device
*/
public function getStudio()
{
return $this->studio;
}
And the ->getNom is just also a basic return, which works fine.
So i get that following error message :
Error: Call to a member function getNom() on a non-object
I've read about lazy loading and I understand why $this->getStudio() gives me a proxy instead of an actual Device object, but I can't go further and use getNom() after that...
I've tried to add fetch : EAGER to avoid lazy loading but it still doesn't work.
Any ideas ?

It looks like property $studio can be NULL. In such a case, you need to validate if it is set. If not, return NULL.
The real code would look like this:
<?php
public function getStudioName(): ?string
{
return $this->studio ? $this->studio->getName() : null;
}

Related

Looping through entities and updating them causes error on flush

I am new to symfony and doctrine. And I am compeleting a code that someone else has started. I mainly have a form for which I wrote a validation function in my controller. In this form a BusReservation object along with its BusReservationDetails are created and saved to the db. so at the end of the form validation function, after the entities are saved in DB, I call a BusReservation Manager method which is transformBusReservationDetailIntoBusTicket which aim is to take each BusReservationDetail in the BusReservation oject and create a a new entity BusTicket based on it.
so I created this loop (please let me know if there is something wrong in my code so that i can write in a good syntax). I tried to put the 3 persist that you see at the end of the code but I got : Notice: Undefined index: 0000000..
I tried to merge (the last 3 lines in code ) I got the following :
A new entity was found through the relationship 'MyBundle\Entity\CustomInfo#busTicket' that was not configured to cascade persist operations for entity: . To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this association in the mapping for example #ManyToOne(..,cascade={"persist"}).
I got this same error when i commented all theh 6 lines of merge and flush.
PS: I am not expecting the flush to fully work. There are some properties that are nullable=false so I assume that I must set them as well so that the entities can be saved to DB. But the error i got is by far different than this.
PS : I noticed that there is a onFlush where the customInfo is updated and persisted again and other things happen, but i am trying to debug step by step. I tried to detach this event but still got the same errors. so I want to fix my code and make sure that the code part that i wrote in the manager is correct and if that's the case then I can move to debugging the event Listener. so please I would like to know if the following code is correct and why the flush is not working.
/**
* #param $idBusReservation
* #return bool
* #throws \Doctrine\ORM\NonUniqueResultException
*/
public function transformBusReservationIntoBusTicket($idBusReservation): bool
{ $result = "into the function";
/** #var BusReservation $busReservation */
$busReservation = $this->em->getRepository('MyBundle:BusReservation')->find($idBusReservation);
if ($busReservation !== null) {
/** #var BusReservationDetail $busReservationDetail */
foreach ($busReservation->getBusReservationDetails() as $busReservationDetail) {
$busTicket = new BusTicket($busReservationDetail->getBusModel(), $busReservation->getPassenger());
$busReservationDetail->setBusTicket($busTicket);
$busTicket->setBusReservationDetail($busReservationDetail);
$busTicket->setOwner($busreservation->getPassenger()->getName());
if ($busReservationDetail->getBusModel()->getCode() === 'VIPbus') {
// perform some logic .. later on
} else {
$customInfo = new CustomInfo();
$customInfo->setNumber(1551998);
// $customInfo->setCurrentMode(
// $this->em->getRepository('MyBundle:Mode')
// ->find(['code' => 'Working'])
// );
$customInfo->setBusTicket($busTicket);
// Bus ticket :
$busTicket->addCustomInfo($customInfo);
$busTicket->setComment($busReservation->getComment());
}
/** #var Mode $currentMode */
$currentMode = $this->em->getRepository('MyBundle:Mode')
->findOneBy(['code' => 'Working']);
$busTicket->setCurrentMode($currentMode);
// $this->em->merge($customInfo);
// $this->em->merge($busReservationDetail);
// $this->em->merge($busTicket);
// $this->em->persist($customInfo);
// $this->em->persist($busReservationDetail);
// $this->em->persist($busTicket);
}
$this->em->flush();
// $this->em->clear();
}
return $result;
}
// *************** In BusReservation.php ********************
/**
* #ORM\OneToMany(targetEntity="MyBundle\Entity\BusReservationDetail", mappedBy="busReservation")
*/
private $busReservationDetails;
/**
* Get busReservationDetails
*
*#return Collection
*/
public function getBusReservationDetails()
{
return $this->busReservationDetails;
}
// ---------------------------------------------------------------------
// *************** In BusReservationDetail.php ********************
/**
* #ORM\ManyToOne(targetEntity="MyBundle\Entity\BusReservation", inversedBy="busReservationDetails")
* #ORM\JoinColumn(name="id_bus_reservation", referencedColumnName="id_bus_reservation", nullable=false)
*/
private $busReservation;
/**
* #ORM\ManyToOne(targetEntity="MyBundle\Entity\BusModel")
* #ORM\JoinColumn(name="bus_model_code", referencedColumnName="bus_model_code", nullable=false)
*/
private $busModel;
/**
* #ORM\OneToOne(targetEntity="MyBundle\Entity\BusTicket", inversedBy="busReservationDetail", cascade={"merge","remove","persist"})
* #ORM\JoinColumn(name="id_bus_ticket", referencedColumnName="id_bus_ticket")
*/
private $busTicket;
/**
* #return BusModel
*/
public function getBusModel()
{
return $this->busModel;
}
//-------------------------------------------------------------------------
// ************ IN BusTicket.php *****************************
/**
* #ORM\OneToMany(targetEntity="MyBundle\Entity\CustomInfo", mappedBy="busTicket")
*/
private $customInfos;
/**
*
* #param CustomInfo $customInfo
*
* #return BusTicket
*/
public function addCustomInfot(CustomInfo $customInfo)
{
if (!$this->customInfos->contains($customInfo)) {
$this->customInfos[] = $customInfo;
}
return $this;
}
/**
* #ORM\OneToOne(targetEntity="MyBundle\Entity\busReservationDetail", mappedBy="busTicket")
*/
private $busReservationDetail;
// --------------------------------------------------------------------
// CUSTOMINFO ENTITY
/**
* #ORM\ManyToOne(targetEntity="MyBundle\Entity\BusTicket", inversedBy="customInfos")
* #ORM\JoinColumn(name="id_bus_ticket", referencedColumnName="id_bus_ticket", nullable=false)
*/
private $busTicket;
The answer is in your error message. You either have to add cascade={"persist"} to your entity annotation, or explicitly call persist. I don't believe you need em->merge() in this situation as you're never taking the entities out of context.
Where you have all your persist lines commented out, just try putting this in
$this->em->persist($busTicket);
$this->em->persist($busReservationDetail);
$this->em->persist($customInfo);
and if you're looping through a ton of entities, you could try adding the flush inside the loop at the end instead of a huge flush at the end.

Symfony2 Form with custom FormType calls DataTransformer with same data in both directions

I have made a new FormType and it extends the entity type via
//...
public function getParent()
{
return 'entity';
}
Which lead my edit form to complain that an integer was not My/Entity/Type and I need a data transformer. So I created one. This is the abbreviated version (it's just the basic tutorial version)
//...
public function reverseTransform($val)
{
// Entity to int
return $val->getId();
}
public function transform($val)
{
// Int to Entity
return $repo->findOneBy($val);
}
//...
Then added it to my form type
//...
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addViewTransformer(new IdToMyModelTransformer($this->em));
}
This fixed me viewing my form, but now when I submit the form with an entity picked from my custom widget it tries to call transform not reverseTransform with the $val as an int the ->getId() fails on a non-object.
I can't figure out the correct way of doing this. If I use 'choice' as my widget parent I get a different set of issues (choice default constraints triggered saying it is invalid data?)
I need an entity passed to my widget so it can extract the meta data for display, but I can't post an entity back of course. How do I tell the form that?
Tried setting 'data_class' => null but no joy. Checking network tab shows the value is sent correctly when posting the form.
Update 1
So I re-read the DataTransformer page and that diagram got me thinking, especially after rubber-duck programming above, I ask the form for Entity but expect it to receive ints.. so I actually need a unidirectional transformer, ViewTransformer -> Get entity for display, get posted an int from widget, don't transform it just pass straight through. Which works and I just get the "invalid data" error on update.
Now I have in my Transformer:
public function transform($val)
{
// Int to Entity
return $repo->findOneBy($val);
}
public function reverseTransform($val)
{
// Do nothing
return $val;
}
Update 2
That seems to have fixed it now, although for some reason if I post int 2 in my form the string "2/" is sent to my transformer. Any ideas on that? FOr now I'm cleaning the string in transformer, but seems like it just shouldnt be happening.
By what I'm seeing in your transformer class you're not implementing the code right. This should be the correct implementation:
namespace App\YourBundle\Form\DataTransformer;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
class IdToMyModelTransformer implements DataTransformerInterface
{
/**
* #var EntityManager
*/
private $em;
/**
* #param EntityManager $em
*/
public function __construct(EntityManager $em) {
$this->em = $em;
}
/**
* Transforms a value from the original representation to a transformed representation.
*
* This method is called on two occasions inside a form field:
*
* 1. When the form field is initialized with the data attached from the datasource (object or array).
* 2. When data from a request is submitted using {#link Form::submit()} to transform the new input data
* back into the renderable format. For example if you have a date field and submit '2009-10-10'
* you might accept this value because its easily parsed, but the transformer still writes back
* "2009/10/10" onto the form field (for further displaying or other purposes).
*
* This method must be able to deal with empty values. Usually this will
* be NULL, but depending on your implementation other empty values are
* possible as well (such as empty strings). The reasoning behind this is
* that value transformers must be chainable. If the transform() method
* of the first value transformer outputs NULL, the second value transformer
* must be able to process that value.
*
* By convention, transform() should return an empty string if NULL is
* passed.
*
* #param mixed $object The value in the original representation
*
* #return mixed The value in the transformed representation
*
* #throws TransformationFailedException When the transformation fails.
*/
public function transform($object) {
if (null === $object) {
return null;
}
return $object->getId();
}
/**
* Transforms a value from the transformed representation to its original
* representation.
*
* This method is called when {#link Form::submit()} is called to transform the requests tainted data
* into an acceptable format for your data processing/model layer.
*
* This method must be able to deal with empty values. Usually this will
* be an empty string, but depending on your implementation other empty
* values are possible as well (such as empty strings). The reasoning behind
* this is that value transformers must be chainable. If the
* reverseTransform() method of the first value transformer outputs an
* empty string, the second value transformer must be able to process that
* value.
*
* By convention, reverseTransform() should return NULL if an empty string
* is passed.
*
* #param mixed $categoryId The value in the transformed representation
*
* #return mixed The value in the original representation
*
* #throws TransformationFailedException When the transformation fails.
*/
public function reverseTransform($id) {
if (!$id || $id <= 0) {
return null;
}
if(!ctype_digit($id)){
throw new TransformationFailedException();
}
$repo = $this->em->getRepository('...');
$result = $repo->findOneBy(array('id' => $id));
if (null === $result) {
throw new TransformationFailedException(
sprintf(
'Entity with id does not exist!',
$id
)
);
}
return $result;
}
}
In the IdToMyIntType you would have something like this:
namespace App\YourBundle\Form\Type;
use App\YourBundle\Form\DataTransformer\IdToMyModelTransformer ;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class IdToMyModelType extends AbstractType {
/**
* #var EntityManager
*/
private $em;
/**
* #param EntityManager $em
*/
public function __construct( EntityManager $em ) {
$this->em = $em;
}
public function buildForm( FormBuilderInterface $builder, array $options ) {
$transformer = new IdToMyModelTransformer ( $this->em );
$builder->addModelTransformer( $transformer );
}
public function setDefaultOptions( OptionsResolverInterface $resolver ) {
$resolver->setDefaults(array('invalid_message' => 'Something went wrong message.'));
}
public function getParent() {
return 'entity';
}
public function getName() {
return 'id_to_model_type';
}
}
I would suggest you check out the DataTransformerInterface and read the documentation over the methods. It'll briefly explain what is that method expected to do. Also, in case you have problems implementing it, you can always check the official documentation, which contains a working example and build up from there.
As per my last update I realised, because I was only using my form data to display the currently saved entity relation (the rest is provided by ajax) and not in the same format the form would be receiving it in it lead to some confusion.
To follow the tutorials wording:
Model data
This was all to remain as-is (No model datatransformer needed)
Norm data
No changes
View data (unidirection transformation required)
Transform()
ID to Entity so widget can access other properties
ReverseTransform()
Posted ID is in correct format so we just return it
Code
Very simplified:
private $om;
public function __construct (ObjectManager om)
{
$this->om = $om;
}
public function transform($val)
{
// Int to Entity
return $om->getRepository('MyBundle:EntityName')->findOneBy($val);
}
public function reverseTransform($val)
{
// Do nothing
return $val;
}
Hopefully that helps anyone else who lets their requirements confuse them!

symfony2 entity customized function: call to undefined function error

I am trying to add 3 functions for slugifying non-ascii characters in the url, but my slugify function doesn't recognize the sanitize() function although they are in the same scope.:|
class Blog
{
/**
* #ORM\OneToMany(targetEntity="Comment", mappedBy="blog")
*/
protected $comments;
...
public function slugify($text)
{
return sanitize($text);
}
...
public function sanitize($title)
{
$title = strip_tags($title);
...
}
...
}
I can't load the fixtures and I get an exception which says "call to undefined function ..\sanitize() in ..\Entity\Blog.php ..."
why this happens? i even tried these: clearing the cache, dropping the database, recreating database, recreating schemas, recreating entities, reloading the fixtures...
but all I've got is the same error:(
can anyone tell me what's wrong with it?
Missing the $this from your return statement. When calling sanitize() without $this it's resolving to the method scope and not the class scope.
class Blog
{
/**
* #ORM\OneToMany(targetEntity="Comment", mappedBy="blog")
*/
protected $comments;
...
public function slugify($text)
{
return $this->sanitize($text);
}
...
public function sanitize($title)
{
$title = strip_tags($title);
...
}
...
}

Found the public method "add", but did not find a public "remove" in symfony2 entity

I get this exeption when I submit my form:
Found the public method "addRemote", but did not find a public "removeRemote" on class App\CoreBundle\Entity\Scene
The weired think is that the remove method exist ...
But i wrote it myself (When I did php app/console doctrine:generate:entities) doctrine didn't generated it. Did I make something wrong ?
/**
* #var array $remote
*
* #ORM\Column(name="remote", type="array", nullable=true)
*/
private $remote;
/**
* Set remote
*
* #param array $remote
* #return Scene
*/
public function addRemote($value, $key=null) {
if($key!=null){
$this->remote[$key] = $value;
}else{
$this->remote[] = $value;
}
return $this;
}
/**
* Remove remote
*/
public function removeRemote(){
unset($this->remote);
}
I allso tried:
/**
* Remove remote
*/
public function removeRemote($key=null){
if($key!=null && array_key_exists($key, $this->remote)){
unset($this->remote[$key]);
}
unset($this->remote);
return $this;
}
You have bigger problem than this; you are abusing your forms :)
Add.. and Remove... methods should be used for relations, not columns as per your code. Also, both add and remove methods must accept parameter that will be either added or removed.
If you still need an array, than getRemotes() method should return key=>value array. Adder and remover will later get that key, based on what user have picked in choice form type.

Symfony2.1 form date field: Argument 1 passed to ... must be an instance of DateTime

My Entity:
/**
* #var \DateTime $publishedAt
*
* #ORM\Column(name="published_at", type="date")
*
* #Assert\Date()
*/
private $publishedAt;
/**
* Set publishedAt
*
* #param \DateTime $publishedAt
* #return MagazineIssue
*/
public function setPublishedAt(\DateTime $publishedAt)
{
$this->publishedAt = $publishedAt;
return $this;
}
/**
* Get published_at
*
* #return \DateTime
*/
public function getPublishedAt()
{
return $this->publishedAt;
}
My form builder:
$builder->add('publishedAt');
My view:
{{ form_widget(form) }}
When I select the date in the selects and submit the form I catche the error:
Catchable Fatal Error: Argument 1 passed to ... must be an instance of DateTime,
string given, called in .../vendor/symfony/symfony/src/Symfony/Component/Form
/Util/PropertyPath.php on line 537 and defined in ... line 214
Why it happens? If I replace the field setter with public function setPublishedAt($publishedAt) I got the error:
Fatal error: Call to a member function format() on a non-object
in .../vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/DateType.php on line 44
If I change the form builder to
$builder->add('publishedAt','date')
all works fine. Why it happens? Why symfony can't guess it and pass to field setter the proper date format (\DateTime instead of string)?
EDIT: if I remove the #Assert\Date() then all works fine too. I think it's a sf2.1 bug with guessing the date field type
I used to deal with this just like Max wrote but then I discovered Data transformers. It's very efficient way and does not imply modifications to model (or it's getter/setter methods)...
EDIT: Check out the title "Using Transformers in a custom field type". They write about DateTime there...
Doctrine want to call \DateTime::format(). From a string.
You can check the argument in the setter method:
public function setPublishedAt($publishedAt)
{
if($publishedAt instanceof \DateTime) {
$this->publishedAt = $publishedAt;
} else {
$date = new \DateTime($publishedAt);
$this->publishedAt = $date;
}
}
To solve this problem you can
1.change the assert from #Assert\Date() to #Assert\Type('\DateTime')
OR
2.change the form builder to $builder->add('publishedAt','date')
OR
3.specify the input option in the form builder: $builder->add('publishedAt',null,array('input' => 'datetime'))

Resources