I have a question concerning Doctrine and entities on Symfony 2.3.
According to the v2.3 "Documentation Book" chapter "Databases and Doctrine" > Saving Related Entities, the example create simultaneously a new row in both product and category tables and associates product.category_id value with the id of the new category item.
The problem is that the controller action creates a new Product and a new Category anytime it is invoked!
In order to just create a new product and associate its category_id with an existing category id, this is the routing.yml route:
acme_store_create_product_by_category:
path: /create/newproduct/{name}/{categoryId}
defaults: { _controller: AcmeStoreBundle:Default:createProduct }
I made a test passing parameters via URL:
/web/store/create/newproduct/Kayak/12
I did something like this which seems working fine:
public function createProductAction($name, $categoryId)
{
$em = $this->getDoctrine()->getManager();
if ( $em->getRepository("AcmeStoreBundle:Category")->findOneById($categoryId) ) {
$product = new Product();
$product->setName($name);
$product->setPrice(220);
$product->setDescription("This is just a test");
$em->persist($product);
$em->flush();
$newproduct = $em->getRepository("AcmeStoreBundle:Product")->find($product->getId());
/** Create new product and populate $newproduct with its data */
$repository = $em->getRepository("AcmeStoreBundle:Category")->find($categoryId);
$newproduct->setCategory($repository);
$em->persist($newproduct);
$em->flush();
/** Update the id_category field of the new product with parameter $categoryId */
//exit(\Doctrine\Common\Util\Debug::dump($product));
return new Response('Create product ' . $name . ' with category id ' . $categoryId);
} else {
return new Response('It doesn\'t exists any category with id ' . $categoryId);
}
}
My doubt in this case is: Is it a good practice to invoke flush() method two times in the same action ? In this case I would like to create a new product selecting the related category from a "list box".
Thank you in advance!
I think it mostly depends on your application domain. If you run flush two times it means you're running two transactions. In the first one you're persisting a product, in the second one a category. So if the first transaction fails (let's say you have a unique key on the product name and you're trying to persist a product with the same name so you get a duplicate key exception) then ask yourself if it's OK to go on and persist a category. I don't think we can answer that easily here because I think it depends on your application logic, what that endpoint is supposed to do, what happens if you end up having a product and not a category or vice-versa.
You should also consider that if you get an exception during the first transaction your code won't handle that error and the second transaction will therefore fail. When an exception like a duplicate key occurs all entities are detached and the entity manager doesn't know anymore how to manage things. So you'll have to reset it or you're going to get an EntityManager is closed issue.
try {
// first transaction
$entityManager->persist($entityOne);
$entityManager->flush();
} catch (\Exception $e) {
/* ... handle the exception */
$entityManager->resetManager();
}
// now we can safely run a second transaction here
I hope this answers your question :-)
I suggest the edited code snippet.
public function createProductAction($name, $categoryId)
{
$em = $this->getDoctrine()->getManager();
if ( $em->getRepository("AcmeStoreBundle:Category")->findOneById($categoryId) ) {
$repository = $em->getRepository("AcmeStoreBundle:Category")->find($categoryId);
$product = new Product();
$product->setName($name);
$product->setPrice(220);
$product->setDescription("This is just a test");
$product->setCategory($repository);
$em->persist($product);
$em->flush();
return new Response('Create product ' . $name . ' with category id ' . $categoryId);
} else {
return new Response('It doesn\'t exists any category with id ' . $categoryId);
}
}
Related
I would like to insert 10 000 rows to database with batch processing.
In first step I need select some objects from databse, then interate these objects and for each of them persist another object to database.
Here is code example:
$em = $this->getDoctrine()->getManager();
$products = $em->getRepository('MyBundle:Product')->findAll(); // return 10 000 products
$category = $em->getRepository('MyBundle:Category')->find(1);
$batchsize = 100;
foreach ($products as $i => $product) {
$entity = new TestEntity();
$entity->setCategory($category);
$entity->setProduct($product); // MyEntity And Product is OneToOne Mapping with foreign key in MyEntity
$em->persist($entity);
if ($i % $batchsize === 0) {
$em->flush();
$em->clear();
}
}
$em->flush();
$em->clear();
It returns this error:
A new entity was found through the relationship 'Handel\GeneratorBundle\Entity\GenAdgroup#product' that was not configured to cascade persist operations for entity
I think problem is in clear(), that remove all objects in memory including $products and $category.
If I use cascade={"persist"} in association, doctrine insert new category row in db.
After some attempts I made some dirty entity errors.
Am I doing sometihng wrong? What is solution and best practice for this job?
Thanks a lot for answer
solution is just to clear only those objects that are changing/creating. Those one that are constant should be left within EntityManager.
Like this
$em->clear(TestEntity::class);
$em->clear(...);
If you left clear without param it will detach all objects that are current under entity manager. Meaning that if you try to reuse them it will throw error as you get. For instance unique filed will be duplicated and trow that error.
After calling
$em->clear();
Category object becomes unpersisted.
You can try calling $em->merge($category) method on it. But probably the most guaranteed way is to fetch it again.
if ($i % $batchsize === 0) {
$em->flush();
$em->clear();
$category = $em->getRepository('MyBundle:Category')->find(1);
}
i wrote an Importer script, which read entries from an csv file,
and iterate the rows. To handle big files without performance loss,
i insert new data within doctrine batch(bulks).
My problem at the moment is, i have an "Category" entity, which should be expanded
only within new entries. So i have to check if entries are available given category names.
My first question is, i've read that doctrines "prePersist" event will be called on call
"$entityManager->persist()" and inside the "flush" method (http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html#prepersist).
So how can i check if the event was inside an flush?
The next thing, how can i update the actually entity within the identity datas?
I try to set the id, but without any effect.
<?php
/**
* #return \Doctrine\Commong\Collections\ArrayCollection
*/
public function getCategories()
{
if (null === $this->categories) {
$this->categories = $this->getServiceCategory()->findAll();
}
return $this->categories;
}
public function prePersist(LifecycleEventArgs $event)
{
$entity = $event->getEntity();
$objectManager = $event->getObjectManager();
if ($entity instanceof \Application\Entity\Category) {
$categories = $this->getCategories();
$entityCriteria = // buildCriteria from Entity;
$matched = $categories->matching($entityCriteria);
if ($matched->count() > 0) {
$entity->setId($matched->first()->getId();
}
}
}
So, here i dont know how to update the persisted categorie entity?
Is this the right event, or should be an other event a better solution for my situation?
I developed the import within zf2 and doctrine2.
regards
First I would recommend to use DQL instead of ORM entities within you import script, because it makes you code much more simple.
Your import process increases the performance, if you first (1.) read all existing "Categories" from yor database, keep them within a member variable and second (2.) iterate each csv row and compare its category foreign key with the initially read set of categories.
If the category already exists in you database, create a new entity row with the existing corresponding foreign key, else create a new category and create a new entity row associated to the new category.
<?php
// read categories from database
$categories = "..."; /* e.g. array('A' => 1,
'B' => 2, ...); */
// ...
// Iterate each csv row
foreach($csvRows as $csvRow) {
// check category name
if(!array_key_exists($csvRow['category']), $categories) {
// Create new Category
// Remember id of the new created category
$categoryId = "...";
} else {
// Use already existing category id
$categoryId = $categories[$csvRow['category']];
}
// Create new csv row entity with category foreign key
// ...
}
?>
I'm trying to delete a record in Doctrine, but I don't know why it's not deleting.
Here is my Code:
function del_user($id)
{
$single_user = $entityManager->find('Users', $id);
$entityManager->remove($single_user);
$entityManager->flush();
}
Plus: How can I echo query to see what going on here?
This is an old question and doesn't seem to have an answer yet. For reference I am leaving that here for more reference. Also you can check the doctrine documentation
To delete a record, you need to ( assuming you are in your controller ):
// get EntityManager
$em = $this->getDoctrine()->getManager();
// Get a reference to the entity ( will not generate a query )
$user = $em->getReference('ProjectBundle:User', $id);
// OR you can get the entity itself ( will generate a query )
// $user = $em->getRepository('ProjectBundle:User')->find($id);
// Remove it and flush
$em->remove($user);
$em->flush();
Using the first method of getting a reference is usually better if you just want to delete the entity without checking first whether it exists or not, because it will not query the DB and will only create a proxy object that you can use to delete your entity.
If you want to make sure that this ID corresponds to a valid entity first, then the second method is better because it will query the DB for your entity before trying to delete it.
For my understanding if you need to delete a record in doctrine that have a doctrine relationship eg. OneToMany, ManyToMany and association cannot be easy deleted until you set the field that reference to another relation equal to null.
......
you can use this for non relation doctrine
$entityManager=$this->getDoctrine()->getManager();
$single_user=$this->getDoctrine()->getRepository(User::class)->findOneBy(['id'=>$id]);
$entityManager->remove($single_user);
$entityManager->flush();
but for relation doctrine set the field that reference to another relation to null
$entityManager=$this->getDoctrine()->getManager();
$single_user=$this->getDoctrine()->getRepository(User::class)->findOneBy(['id'=>$id]);
{# assume you have field that reference #}
$single_user->setFieldData(null);
$entityManager->remove($single_user);
$entityManager->flush();
do you check your entity as the good comment annotation ?
cascade={"persist", "remove"}, orphanRemoval=true
In a Silex route I do like this, in case it helps someone:
$app->get('/db/order/delete', function (Request $request) use ($app) {
...
$id = $request->query->get('id');
$em = $app['orm.em']; //or wherever your EntityManager is
$order = $em->find("\App\Entity\Orders",$id); //your Entity
if($order){
try{
$em->remove($order);
$em->flush();
}
catch( Exception $e )
{
return new Response( $e->getMessage(), 500 );
}
return new Response( "Success deleting order " . $order->getId(), 200 );
}else{
return new Response("Order Not Found", 500);
}
}
You first need repository.
$entityManager->getRepository('Users')->find($id);
instead of
$single_user = $entityManager->find('Users', $id);
'Users' String is the name of the Users repository in doctrine ( depends if you are using Symfony , Zend . . etc ).
First, You may need to check if 'Users' is your fully qualified class name. If not check, and update it to your class name with the namespace info.
Make sure the object returned by find() is not null or not false and is an instance of your entity class before calling EM's remove().
Regarding your other question, instead of making doctrine return SQL's I just use my database (MySQL) to log all queries (since its just development environment).
try a var_dump() of your $single_user. If it is "null", it doens't exist ?
Also check if "Users" is a valid Entity name (no namespace?), and does the $id reference the PK of the user?
If you want to see the queries that are executed check your mysql/sql/... log or look into Doctrine\DBAL\Logging\EchoSQLLogger
What I am trying to do is write a "search" class that can search for a list of products and store them in an array.
I already have a "product" class that can be used to get the details of a specific product.
Here is my code:
class Product {
public $name;
public $price;
public $description;
public function getProductById ($id) {
$sql = 'SELECT name, price, description FROM product WHERE id = ' . $id;
$row = /* MySQL functions here to execute SQL statement and get a matching row */
$this->name = $row['name'];
$this->price = $row['price'];
$this->description = $row['description'];
return TRUE;
}
}
class Search {
public $results;
public $totalResults;
function __construct() {
$this->results = array ();
$this->totalResults = 0;
}
public function doSearch ($name) {
$sql = 'SELECT id FROM product WHERE name LIKE "%' . $name . '%"';
$rows = /* MySQL functions here to execute SQL statement and get a list of matching product ID's */
foreach ($rows as $row) {
$product = new Product;
$product->getProductById ($row['productid']);
$this->results[] = $product;
}
return TRUE;
}
}
$search = new Search;
$search->doSearch ('Fresh Flowers');
The problem with the above is that every matching record in the doSearch method will execute a query in the getProductById method. If there are 100 matching products, there will be 100 individual queries carried out in the Product class.
However, if I get the products directly in the doSearch method using a single query, this will then bypass the Product class altogether.
When a "product" is an object, what's the most appropriate way to write a search class that can return a list of "product" objects without the overhead of what I'm doing above?
Add a constructor to the Product class which takes name, price and description as parameters (or an assoziative array), to populate the object with the necessary values, decoupled of the database query.
Within doSearch, you can then create a SELECT which not only gets the ID but all relevant fields from the products table, and create the populated product objects immediately with new Product($name, $price, $description) or new Product($row), without calling getProductById for each product.
Create a class that populates instances of Product with data from the database.
The class can then create one or multiple instances of the Product class depending on how much data is being fetched.
Conclusion: Extract the getProductById from your Product class and put it somewhere else. It is a specialised method that only populates one instance.
Just grab what you want in the first place.
public function doSearch ($name) {
$sql = 'SELECT id, name, price, description FROM product
WHERE name LIKE "%' . $name . '%"';
// now just return the array
}
Or use PDO to return result sets as objects.
$result->setFetchMode(PDO::FETCH_INTO, new animals);
as discussed here: How can I simply return objects in PDO?
Your Product class shouldn't know anything about a database. It should contain the values representing a product, nothing more. Extract all stuff dealing with the database out of this class.
Searching for products is one way to access a list of products. Your next class should be a list of products then. Only when accessing one single product you'd not have to deal with a list, but this is probably less often than you think.
Ok, you have the product and the list of products, you now can go one step forward and add database access. You need a class that deals with giving you both one product (when searching by id) and lists of products (when searching by some text or other stuff). Only this class allows you to deal with the queries needed to access the database. The result sets of each query may directly be used inside the "list of products" class, probably by inheriting all the stuff that is defined in the general "list of products" class and adding dealing with database results.
So in the end you'll end up having:
Product -> ListOfProducts -> ProductDatabase -> DatabaseAccessLayer
i'm playing a little bit with Symfony2 and Doctrine2.
I have an Entity that has a unique title for example:
class listItem
{
/**
* #orm:Id
* #orm:Column(type="integer")
* #orm:GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #orm:Column(type="string", length="255", unique="true")
* #assert:NotBlank()
*/
protected $title;
now i'm fetching a json and updating my database with those items:
$em = $this->get('doctrine.orm.entity_manager');
foreach($json->value->items as $item) {
$listItem = new ListItem();
$listItem->setTitle($item->title);
$em->persist($listItem);
}
$em->flush();
works fine the first time. but the second time i'm getting an sql error (of course): Integrity constraint violation: 1062 Duplicate entry
sometimes my json file gets updated and some of the items are new, some are not.
Is there a way to tell the entity manager to skip the duplicate files and just insert the new ones?
Whats the best way to do this?
Thanks for all help. Please leave a comment if something is unclear
Edit:
what works for me is doing something like this:
$uniqueness = $em->getRepository('ListItem')->checkUniqueness($item->title);
if(false == $uniqueness) {
continue;
}
$listItem = new ListItem();
$listItem->setTitle($item->title);
$em->persist($listItem);
$em->flush();
}
checkUniqueness is a method in my ListItem Repo that checks if the title is already in my db.
thats horrible. this are 2 database queries for each item. this ends up about 85 database queries for this action.
How about retrieving all the current titles into an array first and checking the inserting title against the current titles in that array
$existingTitles = $em->getRepository('ListItem')->getCurrentTitles();
foreach($json->value->items as $item) {
if (!in_array($item->title, $existingTitles)) {
$listItem = new ListItem();
$listItem->setTitle($item->title);
$em->persist($listItem);
}
}
$em->flush();
getCurrentTitles() would need to be added to ListItem Repo to simply return an array of titles.
This only requires one extra DB query but does cost you more in memory to hold the current titles in an array. There maybe problems with this method if your dataset for ListItem is very big.
If the number of items your want to insert each time isn't too large, you could modify the getCurrentTitles() function to query for all those items with the titles your trying to insert. This way the max amount of $existingTiles you will return will be the size of your insert data list. Then you could perform your checks as above.
// getCurrentTitles() - $newTitles is array of all new titles you want to insert
return $qb->select('title')
->from('Table', 't')
->in('t.title = ', $newTitles)
->getArrayResult();
If you are using an entity that may already exists in the manager you have to merge it.
Here is what I would do (did not test it yet) :
$repository = $this->get('doctrine.orm.entity_manager');
foreach($json->value->items as $item) {
$listItem = new ListItem();
$listItem->setTitle($item->title);
$em->merge($listItem); // return a managed entity
// no need to persist as long as the entity is now managed
}
$em->flush();