Get element from ArrayCollection - symfony

I want to get 1 element out an ArrayCollection. I'm using Symfony 2.7.
For example i have an collection of the entity Activity:
$activities = $em->getRepository('AppBundle:Activity')->findAll();
Next i want to get 1 activity out of this ArrayCollection, based on a many-to-one relation.
The entity 'Activity':
/**
* #ORM\Entity(repositoryClass="AppBundle\Repository\ActivityRepository")
* #ORM\Table(name="activity")
*/
class Activity {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="ObjectElementTask", inversedBy="activities", cascade={"persist"})
* #ORM\JoinColumn(name="objectelementtask_id", referencedColumnName="id", onDelete="CASCADE")
*/
private $objectelementtask;
What did i try:
$objectElementTask = $em->getRepository('AppBundle:ObjectElementTask')->findOneBy(["active" => 1, "object" => (int)$objectId]);
$activity = $activities->findBy(['objectelementtask' => $objectElementTask]);
I get the following exception:
"Call to a member function findBy() on array"
I want to prevent querying the database foreach.
I also tried:
$activity = array_search($objectElementTask, array_column($activities, 'objectelementtask'));
But this has no result...
Thanks in advance!

There are a couple things that I can see here.
1) Doctrine queries return an array not an ArrayCollection. (https://stackoverflow.com/a/8237943/7745506). That's why you're getting the error on findBy. (I don't even think ArrayCollection has a findBy method. Here's the API and I don't see it: http://www.doctrine-project.org/api/common/2.3/class-Doctrine.Common.Collections.ArrayCollection.html) If this were an ArrayCollection, you might be able to use the filter method: http://www.doctrine-project.org/api/common/2.3/source-class-Doctrine.Common.Collections.ArrayCollection.html#377-387
2) If your Activity class has a ManyToOne relationship with the ObjectElementTask class then by definition, any search for an Activity by ObjectElementTask has the potential to return many Activities.
3) You say you don't want to query the DB in a foreach and you don't need to.
What you can do in this situation is query the DB for all Activities (it has the potential to be multiple because of item 2 above) for a specific ObjectElementTask.
$objectElementTask = $em->getRepository('AppBundle:ObjectElementTask')->findOneBy(["active" => 1, "object" => (int)$objectId]);
$activities = $em->getRepository('AppBundle:Activity')->findBy(['objectelementtask' => $objectElementTask]);
This will return all Activities that have that ObjectElementTask as their objectelementtask. You'll have to figure out WHICH Activity you want after that because this will be an array.

Related

Doctrine not generating entity ID

I'm trying to create an entity, but i get a null value on the id of the entity, which is a generated value.
Controller :
if($buRepository->findOneBy(['buName' => $row['Bu1'], 'dateDeleted' => null]) > null)
elseif($buRepository->findOneBy(['buName' => $row['Bu1'], 'dateDeleted' => null]) === null)
{
$bu1 = new Bu();
$bu1->setBuName($row['Bu1']);
$bu1->setLastUpdated(new \DateTime('now'));
$bu1->setDateDeleted(null);
$bu1->setSrcId(3);
$bu1->setIdInSource($bu1->getBuId());
}
Entity :
/**
* #var int
*
* #ORM\Column(name="bu_id", type="bigint", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $buId;
Once my entity tries to flush i get an error on the buid, saying it can't be null.
The Buid isn't created and assign if the entity isn't flushed, the best way to go around it, is to put a trigger in place.
Since IdInSource can't be of NULL value but is suppose to be the BuId (no relation just assigning the INT value), i assigned it a value of 0.
For each entity with a IdInSource of 0 my database trigger replaces it with the BuId.
Try AUTO strategy which fall to AUTO_INCREMENT for MySQL

Symfony: Filter ArrayCollection by associated entity id

I have a User entity and a Usecase entity. This 2 entities are associated by a ManyToMany association, but this association also holds another property, called "environment". To implement this relationship I also have an entity called UserUsecase that has a ManyToOne relationship with User, a ManyToOne relationship with Usecase and the extra field "environment". When fetching a user from the database, his usecases are being fetched as well, so the user has an ArrayCollection of objects of type UserUsecase that represent all the usecases a user has. What I want to do is to filter this ArrayCollection by usecase_id. The UserUsecase class has the structure below:
class UserUsecase
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="userUsecases")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $user;
/**
* #ORM\ManyToOne(targetEntity="Usecase", inversedBy="userUsecases")
* #ORM\JoinColumn(name="usecase_id", referencedColumnName="id")
*/
protected $usecase;
/**
* #ORM\Column(type="integer")
*/
protected $environment;
}
So I tried this inside the User class:
public function filterUsecases($usecase_id){
$criteria = Criteria::create();
$criteria->where(Criteria::expr()->eq('usecase', $usecase_id));
return $this->userUsecases->matching($criteria);
}
It makes sense to me that even the field usecase of the class UserUsecase is an object of type Usecase, it should resolve to its id and the equation would hold when the ids matched. Still this doesn't seem to work, and I cannot find how to implement this filtering. Isn't it possible to be done this way? I found a relevant article that seems to do exactly what I want but this is not working in my case. Here is the article! Am I doing something wrong?
Thanks in advance!
Unless you have many many use cases per user(thousands) I recommend:
public function filterUsecases(Usecase $useCase){
$criteria = Criteria::create();
$criteria->where(Criteria::expr()->eq('usecase', $useCase));
return $this->userUsecases->matching($criteria);
}
Then:
$user->filterUsecases($useCase);
Or passing reference
$user->filterUsecases($em->getReference(Usecase::class, $id));

symfony symfony3 new single ManyToOne object ( owning side of the association )

I have Cases, which also have Attachments. This is coded as a Case entity with the OneToMany association to Attachment entity. The Attachment entity has a ManyToOne association to Case entity. The code:
class Case {
/**
* #var integer
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="Attachment", mappedBy="case",cascade={"persist"})
*/
protected $attachments;
class Attachment
{
/**
* #var integer
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Case", inversedBy="attachments")
* #ORM\JoinColumn(name="case_id", referencedColumnName="id")
*/
protected $case;
}
Im trying the following. I pretend to show/open the whole case into a single page. Inside the page, there will be the attachment list. At the end of that list, I pretend to put a form for new attachment submissions.
So I have written a controller to show the case, and I have created a new attachment form (AttachmentType ) and place it in the middle of the twig template, passing it into the render call of the action as a argument.
// CaseController.php
/**
* #Route("/cases/show/{case}", name="showCase", requirements={ "case" : "\d+" } )
*/
public function showCaseAction(Request $request, $case)
{
$theCase = $this->getDoctrine()->getRepository('AppBundle:Case')->findOneById( $case );
$attachment = new Attachment();
$attachment->setCase( $theCase );
$attachmentForm = $this->createForm(AttachmentType::class, $attachment);
if ( ! $theCase ) {
$this->addFlash('danger', $this->get('translator')->trans('cases.show.case_not_found', [ '%case%' => $case ] ) );
}
return $this->render('cases/caseContainer.html.twig', array( 'case' => $theCase, 'attachmentform' => $attachmentForm->createView() ) );
}
And also I have written a newAttachmentAction into the controller to perform the attachment creation.
I stop writting my code here. I dont want to condition the possible answers.
My problem is that im not able to figure out how to recover the Case object when the newAttachmentAction is called, so I can do the association. I cant figure out if i should place something ( HiddenType,EntityType,etc ) into the Attachment Form Builder to store the Case object, or maybe would be better to use some other Symfony mechanism (Services, Closure, StorageTokens). I have made a wide search along the web, but i have read some many articles, that Im stucked ! Im probably missing the right search keywords.
I hope i have made my self clear, and therefore someone can point me into the right direction to find an example or a tutorial.
Best regards and many many thanks for your time and attention !
For creating a Case, i will add a HiddenType for attachment property inside the CaseformType.
Set data_class to Attachment
When you will create the form, you will pass a new instance of Case with the attachment reference.
After the post, when you will receive the form data, you will have the linked object

Symfony entities without relational

I work with Symfony2 and Doctrine and I have a question regarding entities.
In a performance worries, I'm wondering if it is possible to use an entity without going all the associations?
Currently, I have not found another way to create a model inheriting the class with associations and associations specify NULL in the class that inherits.
thank you in advance
OK, a little detail, it's for a API REST (JSON).
This is my class :
/**
* Offerequipment
*
* #ORM\Table(name="offer_equipment")
* #ORM\Entity(repositoryClass="Charlotte\OfferBundle\Repository\Offerequipment")
*/
class Offerequipment
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Charlotte\OfferBundle\Entity\Offer")
* #ORM\JoinColumn(name="offer_id", referencedColumnName="id")
*/
private $offer;
/**
* #ORM\ManyToOne(targetEntity="Charlotte\ProductBundle\Entity\Equipment")
* #ORM\JoinColumn(name="equipment_id", referencedColumnName="id")
*/
private $equipment;
/**
* #VirtualProperty
*
* #return String
*/
public function getExample()
{
return $something;
}
and with QueryBuilder method, i can't get my virtual properties or getters.
Thanks for your help :)
Look at Serialization.
By serialising your entities, you can choose to exclude or expose a property of an entity when you render it.
Look at the Symfony built-in Serializer and/or JMSSerializer.
Otherwise, you can use QueryBuilder and DQL to choose what fields you want to fetch in your queries.
Like this, you can make your own find method in the Repository of your entities.
// src/AcmeBundle/Repository/FooRepository
class FooRepository extends \Doctrine\ORM\EntityRepository
// ...
public function find($id) {
$queryBuilder = $this->createQueryBuilder('e')
->select('e.fieldA', 'e.fieldB') // selected fields
->where('e.id = :id') // where statement on 'id'
->setParameter('id', $id);
$query = $queryBuilder->getQuery();
$result = $query->getResult();
}
// ...
}
Don't forget define the Repository in the corresponding Entity.
/**
* Foo.
*
* #ORM\Entity(repositoryClass="AcmeBundle\Repository\FooRepository")
*/
class Foo
{
// ...
}
By default Doctrine will not automatically fetch all of the associations in your entities unless you specifically each association as EAGER or unless you are using a OneToOne association. So if you are looking to eliminate JOINs, you can just use Doctrine in its default state and it won't JOIN anything automatically.
However, you this will not alleviate all of your performance concerns. Say, for example, you are displaying a list of 50 products in your application on a single page and you want to show their possible discounts, where discounts are an association on your product entity. Doctrine will create 50 additional queries just to retrieve the discount data unless you explicitly join the discount entity in your query.
Essentially, the Symfony profiler will be your friend and show you when you should be joining entities on your query - don't just think that because you aren't joining associations automatically that your performance will always be better.
Finally, after many days, I've found the solution to select only one entity.
VirtualProperties are found :)
public function findAllByOffer($parameters)
{
$queryBuilder = $this->createQueryBuilder('oe');
$queryBuilder->select('oe, equipment');
$queryBuilder->join('oe.equipment', 'equipment');
$result = $queryBuilder->getQuery()->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true)->getResult();
return $result;
}

Composite key and form

I have the following associations in my database (simplified version):
This is a Many-To-Many association but with an attribute on the joining table, so I have to use One-To-Many/Many-To-One associations.
I have a form where I can add as many relations as I want to one order item and create it at the same time (mainly inspired by the How to Embed a Collection of Forms tutorial from the documentation.
When I post the form, I get the following error:
Entity of type TEST\MyBundle\Entity\Relation has identity through
a foreign entity TEST\MyBundle\Entity\Order, however this entity
has no identity itself. You have to call EntityManager#persist() on
the related entity and make sure that an identifier was generated
before trying to persist 'TEST\MyBundle\Entity\Relation'. In case
of Post Insert ID Generation (such as MySQL Auto-Increment or
PostgreSQL SERIAL) this means you have to call EntityManager#flush()
between both persist operations.
I understand this error because Doctrine tries to persist the Relation object(s) related to the order since I have the cascade={"persist"} option on the OneToMany relation. But how can I avoid this behavior?
I have tried to remove cascade={"persist"} and manually persist the entity, but I get the same error (because I need to flush() order to get the ID and when I do so, I have the same error message).
I also tried to detach() the Relation objects before the flush() but with no luck.
This problem seems unique if 1) you are using a join table with composite keys, 2) forms component, and 3) the join table is an entity that is being built by the form component's 'collection' field. I saw a lot of people having problems but not a lot of solutions, so I thought I'd share mine.
I wanted to keep my composite primary key, as I wanted to ensure that only one instance of the two foreign keys would persist in the database. Using
this entity setup as an example
/** #Entity */
class Order
{
/** #OneToMany(targetEntity="OrderItem", mappedBy="order") */
private $items;
public function __construct(Customer $customer)
{
$this->items = new Doctrine\Common\Collections\ArrayCollection();
}
}
/** #Entity */
class Product
{
/** #OneToMany(targetEntity="OrderItem", mappedBy="product") */
private $orders;
.....
public function __construct(Customer $customer)
{
$this->orders = new Doctrine\Common\Collections\ArrayCollection();
}
}
/** #Entity */
class OrderItem
{
/** #Id #ManyToOne(targetEntity="Order") */
private $order;
/** #Id #ManyToOne(targetEntity="Product") */
private $product;
/** #Column(type="integer") */
private $amount = 1;
}
The problem I was facing, if I were building an Order object in a form, that had a collection field of OrderItems, I wouldn't be able to save OrderItem entity without having saved the Order Entity first (as doctrine/SQL needs the order id for the composite key), but the Doctrine EntityManager wasn't allowing me to save the Order object that has OrderItem attributes (because it insists on saving them en mass together). You can't turn off cascade as it will complain that you haven't saved the associated entities first, and you cant save the associated entities before saving Order. What a conundrum. My solution was to remove the associated entities, save Order and then reintroduce the associated entities to the Order object and save it again. So first I created a mass assignment function of the ArrayCollection attribute $items
class Order
{
.....
public function setItemsArray(Doctrine\Common\Collections\ArrayCollection $itemsArray = null){
if(null){
$this->items->clear();
}else{
$this->items = $itemsArray;
}
....
}
And then in my Controller where I process the form for Order.
//get entity manager
$em = $this->getDoctrine()->getManager();
//get order information (with items)
$order = $form->getData();
//pull out items array from order
$items = $order->getItems();
//clear the items from the order
$order->setItemsArray(null);
//persist and flush the Order object
$em->persist($order);
$em->flush();
//reintroduce the order items to the order object
$order->setItemsArray($items);
//persist and flush the Order object again ):
$em->persist($order);
$em->flush();
It sucks that you have to persist and flush twice (see more here Persist object with two foreign identities in doctrine). But that is doctrine for you, with all of it's power, it sure can put you in a bind. But thankfully you will only have to do this when creating a new object, not editing, because the object is already in the database.
You need to persist and flush the original before you can persist and flush the relationship records. You are 100% correct in the reason for the error.
I assume from the diagram that you are trying to add and order and the relation to the contact at the same time? If so you need to persist and flush the order before you can persist and flush the relationship. Or you can add a primary key to the Relation table.
I ended up creating a separated primary key on my Relation table (instead of having the composite one).
It looks like it is a dirty fix, and I am sure there is a better way to handle this situation but it works for now.
Here is my Relations entity:
/**
* Relation
*
* #ORM\Entity
*/
class Relation
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Contact", inversedBy="relation")
*/
protected $contact;
/**
* #ORM\ManyToOne(targetEntity="Order", inversedBy="relation")
*/
protected $order;
/**
* #var integer
*
* #ORM\Column(name="invoice", type="integer", nullable=true)
*/
private $invoice;
//Rest of the entity...
I then added the cascade={"persist"} option on the OneToMany relation with Order:
/**
* Orders
*
* #ORM\Entity
*/
class Order
{
/**
* #ORM\OneToMany(targetEntity="Relation", mappedBy="order", cascade={"persist"})
*/
protected $relation;
//Rest of the entity...
Et voilĂ !

Resources