Flow3 PHP: Add item to empty collection - php

I searched a while, but until now, I wasn't able to find a suitable answer.
In my "Offer" class, I have the following:
/**
* #ORM\OneToMany(mappedBy="offer")
* #var Collection<OfferItem>
*/
protected $offerItems;
/**
* #return Collection
*/
public function getOfferItems()
{
return $this->offerItems;
}
/**
* #param Collection $offerItems
*/
public function setOfferItems($offerItems)
{
$this->offerItems = $offerItems;
}
Now, I create a new Offer and would like to add some OfferItems as well:
$offer = new Offer();
$offerItem = new OfferItem();
$offer->getOfferItems()->add($offerItem);
But then, the error comes: "Fatal error: Call to a member function add() on null". Okay, in some points, it makes sense - the collection is empty until know - and perhaps "null".
I'm not such an PHP / Flow3 / Doctrine expert, to have the overview, how to handle such an sitation?
I think, I have to set an empty (but not null-) collection to the offer. But
$collection = new \Doctrine\Common\Collections\Collection()
Is not working, because "Collection" is an interface.
Any hint, idea or something like that, to understand my problem would be nice.
Thank you very much in advance for your help!

Related

symfony/doctrine: cannot use object without refreshing

It's the first time I run into this problem. I want to create a doctrine object and pass it along without having to flush it.
Right after it's creation, I can display some value in the object, but I can't access nested object:
$em->persist($filter);
print_r($filter->getDescription() . "\n");
print_r(count($filter->getAssetClasses()));
die;
I get:
filter description -- 0
(I should have 19 assetClass)
If I flush $filter, i still have the same issue (why oh why !)
The solution is to refresh it:
$em->persist($filter);
$em->flush();
$em->refresh($filter);
print_r($filter->getDescription() . " -- ");
print_r(count($filter->getAssetClasses()));
die;
I get:
filter description -- 19
unfortunately, you can't refresh without flushing.
On my entities, I've got the following:
in class Filter:
public function __construct()
{
$this->filterAssetClasses = new ArrayCollection();
$this->assetClasses = new ArrayCollection();
}
/**
* #var Collection
*
* #ORM\OneToMany(targetEntity="FilterAssetClass", mappedBy="filterAssetClasses", cascade={"persist"})
*/
private $filterAssetClasses;
public function addFilterAssetClass(\App\CoreBundle\Entity\FilterAssetClass $filterAssetClass)
{
$this->filterAssetClasses[] = $filterAssetClass;
$filterAssetClass->setFilter($this);
return $this;
}
in class FilterAssetClass:
/**
* #var Filter
*
* #ORM\ManyToOne(targetEntity="App\CoreBundle\Entity\Filter", inversedBy="filterAssetClasses")
*/
private $filter;
/**
* #var Filter
*
* #ORM\ManyToOne(targetEntity="AssetClass")
*/
private $assetClass;
public function setFilter(\App\CoreBundle\Entity\Filter $filter)
{
$this->filter = $filter;
return $this;
}
Someone else did write the code for the entities, and i'm a bit lost. I'm not a Doctrine expert, so if someone could point me in the good direction, that would be awesome.
Julien
but I can't access nested object
Did you set those assetClasses in the first place?
When you work with objects in memory (before persist), you can add and set all nested objects, and use those while still in memory.
My guess is that you believe that you need to store objects to database in order for them to get their IDs assigned.
IMHO, that is a bad practice and often causes problems. You can use ramsey/uuid library instead, and set IDs in Entity constructor:
public function __construct() {
$this->id = Uuid::uuid4();
}
A database should be used only as a means for storing data. No business logic should be there.
I would recommend this video on Doctrine good practices, and about the above mentioned stuff.
Your problem is not related to doctrine nor the persist/flush/refresh sequence; the problem you describe is only a symptom of bad code. As others have suggested, you should not be relying on the database to get at your data model. You should be able to get what you are after entirely without using the database; the database only stores the data when you are done with it.
Your Filter class should include some code that manages this:
// Filter
public function __contsruct()
{
$this->filterAssetClasses = new ArrayCollection();
}
/**
* #ORM\OneToMany(targetEntity="FilterAssetClass", mappedBy="filterAssetClasses", cascade={"persist"})
*/
private $filterAssetClasses;
public function addFilterAssetClass(FilterAssetClass $class)
{
// assuming you don't want duplicates...
if ($this->filterAssetClasses->contains($class) {
return;
}
$this->filterAssetClasses[] = $class;
// you also need to set the owning side of this relationship
// for later persistence in the db
// Of course you'll need to create the referenced function in your
// FilterAssetClass entity
$class->addFilter($this);
}
You may have all of this already, but you didn't show enough of your code to know. Note that you should probably not have a function setFilterAssetClass() in your Filter entity.

How to create one property based in another property of the same document?

I have a simple document that has its ID:
/**
* #MongoDB\Id(strategy="increment")
* #Serializer\Since("2.0")
*/
protected $id;
and a property code
/**
* #var string
*
*/
protected $code;
I want that the code be generated based in the ID. So I am planning to define it in the constructor
public function __construct()
{
$this->code = (string)$this->id.(string)rand(0,1000);
}
My question is, as both are defined in the same php class, there would be any risk to define one based in another?
Any risk of the code ask for the id before it was defined? Or there is any better way of doing such thing?
Your ID will be null when you create the object. To create code property you should set it after the persist. Something like this:
$dm = $this->get('doctrine.odm.mongodb.document_manager');
$item = new Item();
$item->setSomeValue($someValue);
$dm->persist($item);
$dm->flush();
$item->setCode($item->getId());
$dm->persist($item);
$dm->flush();
As you can imagine, this is not a good practice and you should avoid it. Generate values from database ID it is not a good idea too. I recommend you to use functions like uniqid to do the workaround. Uniqid is safer, faster and cleaner.
if you just want the serialized obj to be like:
{
id:123,
code:123
}
u can just add a getter function and it will be included in serialized result
/**
* #var string
*
*/
protected $code;
public function getCode(){
return $this->getId();
}
if this does not work automagicaly (should) you can use annotations for serializer:
/**
* #JMS\Expose
* #JMS\Accessor(getter="getCode")
*/
private $code;
if you want to persist the code property you can do it like Vangoso, but it does not realy make sense storing the same information twice

Illegal offset type in isset or empty Doctrine clear

I search for hours to find solution for this problem but without luck.
Maybe this is duplicate post, but I didn't find it.
So, I have problem in Symfony's Service, when I call entity manager clear($entityname) method I got this error:
Warning: Illegal offset type in isset or empty.
I have no clue why this happening.
If i comment clear() method, everything works fine.
First I call ProductController
public function postProductAction(Request $request)
{
if($jsonData = $this->requestToArray($request)){
$productHandler = $this->get("admin.product_handler");
$insertNews = $productHandler->insert($jsonData);
if($insertNews === true) {
return $this->jsonResponse->setData(array(
"status" => true, "msg" => MessageConstants::SUCCESS_SEND_DATA));
}
}
$this->jsonResponse->setStatusCode(204);
return $this->jsonResponse->setData(
array("status" => false, "msg" => MessageConstants::FAILED_RECEIVE_RESPONSE));
}
Then call ProductHandler which set new product
$productRepo = new Products();
$productRepo->setCarmodels(json_encode($data['ModelId']));
$productRepo->setCategoryid($category);
$productRepo->setDescription($data['Description']);
$productRepo->setDiscount($data['Discount']);
$productRepo->setDiscountprice($data['DiscountPrice']);
$this->em->persist($productRepo);
$this->em->flush($productRepo);
$insertOther = $this->update($productRepo->getId(), $data);
$this->em->clear($productRepo);
insertAnotfer call update,because there is some action which require to get product id, so I need first to insert then do update.
$product = $productRepo->find((int)$id);
$product->setCarmodels(json_encode($data['ModelId']));
$product->setCategoryid($category);
$product->setDescription($data['Description']);
$product->setDiscount($data['Discount']);
$product->setDiscountprice($data['DiscountPrice']);
In update I also call clear method
$this->em->flush($product);
$this->em->clear($product);
And then I get this error. I tried to remove clear method from update but no luck. Error will not be trigger only if I set clear() method without entity in insert function.
The EntityManager::clear method takes the name of an entity type, not an actual instance of an entity:
\Doctrine\ORM\EntityManager:
/**
* Clears the EntityManager. All entities that are currently managed
* by this EntityManager become detached.
*
* #param string|null $entityName if given, only entities of this type will get detached
*
* #return void
*/
public function clear($entityName = null)
{
$this->unitOfWork->clear($entityName);
}
It seems like what you need is detach - the correct method for disassociating an entity instance from the entity manager:
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/working-with-objects.html#detaching-entities
\Doctrine\ORM\EntityManager:
/**
* Detaches an entity from the EntityManager, causing a managed entity to
* become detached. Unflushed changes made to the entity if any
* (including removal of the entity), will not be synchronized to the database.
* Entities which previously referenced the detached entity will continue to
* reference it.
*
* #param object $entity The entity to detach.
*
* #return void
*
* #throws ORMInvalidArgumentException
*/
public function detach($entity)
{
if ( ! is_object($entity)) {
throw ORMInvalidArgumentException::invalidObject('EntityManager#detach()' , $entity);
}
$this->unitOfWork->detach($entity);
}
I don't believe you should have the parameter specified within either the flush or clear functions.
Have you tried like this (without entity as parameter):
$this->em->flush();
$this->em->clear();
I'm not certain if that will work for you, but can you try it and see if that helps? I could be wrong about this though, so if someone has a better suggestion, please post.

Doctrine won't save one-to-many relationship

I'm being bugged by an issue that seems very very puzzling. FYI - I know and I have read most of the doctrine questions around here, so I know the basics of doctrine and specifying relationships.
Below is how my data model looks (posting relevant sections of code)
class Sample
{
/**
* #ORM\OneToMany(targetEntity="Analysis", mappedBy="sample", cascade={"persist"})
*
protected $analyses
public function addAnalysis(Analysis $analysis)
{
$analyses->setSample($this);
$this->analyses[] = $analyses;
}
}
And Analysis
class Analysis
{
/**
* #ORM\ManyToOne(targetEntity="Sample", inverseBy="analyses", cascade={"persist"})
* #ORM\JoinColumn(name="sample_id", referencedColumnName="id")
*
protected $sample
public function setSample(Sample $sample)
{
$this->sample = $sample;
}
}
So one Sample can have multiple Analysis. While creating a new Analysis however, it is not letting me create one. It is throwing a NOT NULL constraint exception.
Below is the code I tried.
$analysis = new Analysis
$analysis->setUUID("seeebbbfg");
$analysis->setStatus(Analysis::STATUS_DONE);
$sample = $sample->addAnalysis($analysis)
$em->persist($sample);
$em->flush();
I have gone through many links and the doctrine documentation
Doctrine One-To-Many Relationship Won't Save - Integrity Constraint Violation
many-relationship-wont-save-integrity-constraint-violation
Symfony 2 doctrine persist doesn't work after updating Relationship Mapping
Doctrine entities relationship
After going through this Doctrine "A new entity was found through the relationship" error, I tried to persist $analysis before persisting sample, but it gave an 'a new entity was found' error and then this official doctrine documentation
http://doctrine-orm.readthedocs.io/projects/doctrine-orm/en/latest/reference/association-mapping.html
Not sure what I'm missing. Can anyone shed any light on this?
UPDATE 1
[Doctrine\DBAL\Exception\NotNullConstraintViolationException]
An exception occurred while executing
INSERT INTO analysis (id, uuid, type, status, submission_at, sample_id) VALUES (?,?,?,?,?,?) with params [202066, "seeebbbfg", "temp", 2, "2016-5-22 12:16:39", null]
null value in column "sample_id" violates not-null constraint
I think you should add analysisto the Analyses collection before set Sample.
I guess $this->analyses is an ArrayCollection so, use the ArrayCollection::add() method to add a new object.
Please, try this part of code and let me know the result
class Sample
{
/**
* #ORM\OneToMany(targetEntity="Analysis", mappedBy="sample", cascade={"persist"})
*/
protected $analyses
public function __construct()
{
$this->analyses = new ArrayCollection();
}
public function addAnalysis(Analysis $analysis)
{
$this->analyses->add($analysis);
$analysis->setSample($this);
return $this;
}
}
Credreak...
Thanks for this... Doctrine is not very clear how these are constructed.
I actually modified your code to create a instance of "related field" (analyese) as an Array Collection # _construct and then you can populate that Array with the actual Entity by passing it to the addAnalysis() function.
So the order of operations is: (as I understand it)
Construct an instance of Sample w/ an "related field" (analyese) Array
Construct an instance of Analysis and populate the Array
Pass the Analysis Array to the Sample analyese Array
Then flush the sample which will save the Analysis data to the database.
This worked for me.
class Sample
{
/**
* #ORM\OneToMany(targetEntity="Analysis", mappedBy="sample", cascade={"persist"})
*/
protected $analyses
public function __construct()
{
$this->analyses = new ArrayCollection();
}
public function addAnalysis(Analysis $analysis)
{
$this->analyses->add($analysis);
$analyses->setSample($this);
return $this;
}
}

PHPStorm Type hinting array of different types

Is it possible in PHPStorm to type hint an array with different object types, ie:
public function getThings()
{
return array (new Thing(), new OtherThing(), new SomethingElse());
}
Even declaring them separately before building the array doesn't seem to work.
you can use phpdocs in order for phpstorm to accept an array of multiple types like so:
/**
* #return Thing[] | OtherThing[] | SomethingElse[]
*
*/
public function getThings()
{
return array (new Thing(), new OtherThing(), new SomethingElse());
}
This technique will make phpstorm think that the array could contain any of those objects and so it will give you type hinting for all three.
Alternatively you can make all of the objects extend another object or implement an interface and type hint that once object or interface like so:
/**
* #return ExtensionClass[]
*
*/
public function getThings()
{
return array (new Thing(), new OtherThing(), new SomethingElse());
}
This will give you type hints for only what the classes extend or implement from the parent class or interface.
I hope this helped!
This is described in the PHPDoc standards
https://github.com/phpDocumentor/fig-standards/blob/master/proposed/phpdoc.md#713-param
/**
* Initializes this class with the given options.
*
* #param array $options {
* #var bool $required Whether this element is required
* #var string $label The display name for this element
* }
*/
public function __construct(array $options = array())
{
<...>
}
In PHP, I've seen a very nice way of doing this:
#return array<Thing,OtherThing,SomethingElse>
IDEs like PHPStorm and VSCode understand this syntax pretty well. Hope this helps.

Categories