I am, trying to increment the field, and get no results;
Here is my code:
class Query
{
/**
* #ODM\Id
*/
protected $id;
/**
* #ODM\Field(type="int", strategy="increment")
*
*/
protected $my_id = 0;
public function incrementMyId()
{
$this->my_id++;
}
}
When in action i try:
$query = new Query();
$query->incrementMyId();
$this->documentManager->persist($query);
$this->documentManager->flush();
The field my_id is always equals to int(1);
Can u help me with this issue? Thanks.
I use ZF3,
"alcaeus/mongo-php-adapter": "^1.
"doctrine/doctrine-mongo-odm-module": "^0.11.
"doctrine/mongodb-odm": "^1.1"
Strategy increment for a normal field means that the query updating document in the database will use $inc operator instead of $set, exactly as per documentation.
You seem to want to have both auto-generated ObjectId identifier and unique auto-incremented one for the entire collection. For such usage please see my other answer to a similar question
Related
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.
In order to solve a problem I asked about earlier, I am trying to create a custom repository function that will determine whether an instance of Repair is unique, based on the device, name, and colors constraints.
Here's my Doctrine Annotation for class Repair. Mind that the device property is Many To One (many Repairs for one Device), and that colors is Many to Many.
/**
* #ORM\Table(name="repair")
* #ORM\Entity(repositoryClass="AppBundle\Repository\RepairRepository")
* #UniqueEntity(fields={"name", "device", "colors"}, repositoryMethod="getSimilarRepairs", message="Repair {{ value }} already exists for this name, device and colour combination.")
*/
This is my RepairRepository.php, in which $criteria['colors'] is an array.
public function getSimilarRepairs(array $criteria) {
$builder = $this->createQueryBuilder('r')
->where('r.device = :device')
->andWhere('r.colors = :colors')
->andWhere('r.name = :name')
->setParameters(['deviceid'=>$criteria['device'],'colors'=>$criteria['colors'],'name'=>$criteria['name']]);
return $builder->getQuery();
}
I have three problems that can probably be brought back to one:
editing: with every change, causing a duplicate or not, I get the message that a duplicate entity exists.
editing: despite the error message, name changes are performed anyway!
adding: I can create as many duplicates as I like, there never is an error message.
Your problem is that the colors relation is a ManyToMany.
In SQL you can not query '=' on this relation.
It is very complicated, that's why Doctrine (and we probably) can't make it alone .
A partial solution to build a query :
public function getSimilarRepairs(array $criteria) {
$builder = $this->createQueryBuilder('r')
->where('r.device = :device')
->andWhere('r.name = :name')->setParameter('name',$criteria['name'])
->andWhere('r.colors = :colors')->setParameter('deviceid',$criteria['device']);
// r matches only if each of your colors exists are related to r :
$i=0;
foreach($criteria['colors'] as $color){
$i++;
$builder->join('r.colors','c'.$i)->andWhere('c = :color'.$i)->setParameter('color'.$i,$color);
}
// Then you had also to check than there is no other color related to r :
// I don't know how
return $builder->getQuery();
}
But let me propose another solution :
In your repair entity, your can store a duplicate of your related colours :
/**
* #var string
*
* #ORM\Column(name="name_canonical", type="string")
*/
private $serializedColors;
set it with doctrine lifecycle events :
/**
* #ORM\PrePersist
* #ORM\PreUpdate
*/
public function updateColors()
{
$serializedColors = '';
foreach($this->colors as $color){
$serializedColors .= $color->getId().'#';
}
$this->serializedColors = $serializedColors;
}
Don't forget to add #HasLifecycleCallbacks
Then change your UniqueEntityConstraint to fields={"name", "device", "serializedColors"}, forget the custom query, and it will work.
i have the following document:
class Purchase
{
/**
* #MongoDB\Id(strategy="INCREMENT")
*/
protected $id;
...
I'm using this document in a Symfony 2.8.4 project.
In this case, the ID for the first document that i persist is '1', the next one will be '2' and so on.
I'd like to start the counter from 1000, but i can't figure how i can do it inside the "Model part".
Thanks
Unfortunately there is now way to set counter in "Model part", but as the current counters are stored in the database you may alter their values there. For more details how this work you can inspect how IncrementGenerator::generate works.
It could be a perfect use of the SequenceGenerator, but not supported by the platform.
One work-around (out of the model, unfortunately) consists in setting the first ID manually.
In your controller:
$object = new Purchase();
$em = $this->getDoctrine()->getManager():
$metadata = $em->getClassMetadata(get_class($object));
$metadata->setIdGeneratorType($metadata::GENERATOR_TYPE_NONE);
$object->setId(1000);
$em->persist($object);
$em->flush();
print $object->getId(); // 1000
Don't forget to add the setter function in your Purchase document:
/**
* #param int $id
*
* #return Purchase
*/
public function setId($id)
{
$this->id = $id;
return $this;
}
Then, remove this logic (or wrap it in a check to verify that the object is the first being persisted).
The inner drawback with senquenced identifier for a SQL table is the possiblity for a end user to easily go all over your tables. Sometimes it is a problem.
One solution is to create a non-sequenced id, something non guessable for every row.
This id must be a unique field, obviously. I can use a random function to generate thoses uniques ids for every row but there is a probability that it collides with a previously set id. If it collides, the end user will perceive it as random bug...
Here is one simple solution to overcome this problem:
$keyValid = false;
while(!$keyValid) {
// Create a new random key
$newKey = generateKey();
// Check if the key already exists in database
$existingPotato = $em->getRepository('Potato')->findOneBy(array('key' => $newKey));
if (empty($existingPotato)) {
$keyValid = true;
}
}
// Hooray, key is unique!
It forces me to make at least one SELECT statement everytime I want a new id.
So, is there a better, widely-accepted solution to this problem?
Alternatively, is there an optimised length for the id that make this problem irrelevant by making the collision probability negligable (for a 3,000,000 rows table)?
You can add a Custom id generation strategy to do it. You can implement it by creating a class that extends AbstractIdGenerator:
use Doctrine\ORM\Id\AbstractIdGenerator;
class NonSequencedIdGenerator extends AbstractIdGenerator
{
public function generate(\Doctrine\ORM\EntityManager $em, $entity)
{
$class = $em->getClassMetadata(get_class($entity));
$entityName = $class->getName();
do {
// You can use uniqid(), http://php.net/manual/en/function.uniqid.php
$id = generateKey();
} while($em->find($entityName, $id));
return $id;
}
}
Then add it using annotations in your entity class:
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="CUSTOM")
* #ORM\CustomIdGenerator(class="NonSequencedIdGenerator")
*/
private $id;
But if your generateKey don't return an unique identifier you should check if it already exists anyway. To avoid this, you can use an UUID generator for the primary keys in your entity as well.
/**
* #ORM\Id
* #ORM\Column(type="guid", unique=true)
* #ORM\GeneratedValue(strategy="UUID")
*/
private $id;
If you don't like this, you can create a new custom id generation that use UUID_SHORT, and use function like this to make it shorter.
use Doctrine\ORM\Id\AbstractIdGenerator;
class UuidShortGenerator extends AbstractIdGenerator
{
public function generate(EntityManager $em, $entity)
{
$conn = $em->getConnection();
return $conn->query('SELECT UUID_SHORT()')->fetchColumn(0);
}
}
The problem here is that I don't think it's provides full portability.
Based on this post: How to set the id of a foreign key id #sf2 #doctrine2
In the previous post I found this solution
class Item
{
/**
* #ORM\ManyToOne(targetEntity="MyBundle\Entity\ItemType", inversedBy="itemTypes")
* #ORM\JoinColumn(name="type_id", referencedColumnName="id")
*/
protected $item_type;
/**
*
* #var string $item_type_id
* #ORM\Column(type="integer")
*/
protected $item_type_id;
}
.... Setter & Getter
}
Which allows me to do something like that
$item = new Item();
$item->setItemTypeId(2); // Assuming that the ItemType with id 2 exists.
But from the last update of doctrine2.3 it's not working anymore.
when I persist the item(so creating the INSERT SQL query), it does not set the item_type_id field. only all other fields.
Any idea how to set manually the item_type_id without retrieve the ItemType just before setting it ? it's quite over use of queries !?
$item = new Item();
$itemType = $this->entity_manager->getRepository('Acme\MyBundle:ItemType')->find(2);
$item->setItemType($itemType); // Assuming that the ItemType with id 2 exists.
I've found the solution of this problem.
As we are working with an ORM, we usually do not use the identifier of an element and just work with the object itself.
But sometimes it is convenient to use the object ids instead of the objects, for example when we store an identifier in the session (ex: user_id, site_id, current_process_id,...).
In these circumstances, we should use Proxies, I'll refer to the Doctrine documentation for more information:
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/advanced-configuration.html#reference-proxies
In this example we would then have something like this:
$itemTypeId = 2; // i.e. a valid identifier for ItemType
$itemType = $em->getReference('MyProject\Model\ItemType', $itemTypeId);
$item->setItemType($itemType);
Hope it will help others.