I'm trying to add the current user_id into a created_by or updated_by field automatically.
But I can't seem to get the current user_id into the entity of the other table.
I'm using FOSUserBundle....
/**
* #ORM\PrePersist
*/
public function setCreatedByValue()
{
if(!$this->getCreatedBy())
{
$user= $this->container->get('security.context')->getToken()->getUser();
$this->created_by = $user->getId();
}
}
This throws a Undefined property: ..... Container error :-(
How do I access the current user id?
Thanks for any help.
Your entity does not have any reference to the container. Where is this $this->container coming from ? From nowhere.
There would many ways to resolve the problems, but it's better to implement what you need than fixing your problems :)
So I would suggest a Doctrine listener, that would modify the createdBy value every time an entity is created.
In fact, it already exist: https://github.com/KnpLabs/DoctrineBehaviors#blameable
Take a look at implementation: https://github.com/KnpLabs/DoctrineBehaviors/blob/master/src/Knp/DoctrineBehaviors/ORM/Blameable/BlameableListener.php#L151
Related
Iam using Symfony(4.3) and Doctrine. When I want to get a user like this in the constructor of the script:
$userRepo = $this->em->getRepository(User::class);
$this->systemUser = $userRepo->findOneBy([
"firstName" => "system",
"lastName" => "system",
]);
And save the reference to this user for example at a createdBy-field like this:
$newUser->setName("test");
$newUser->setCreatedBy($this->systemUser);
$this->entityManager->persist($newUser);
$this->entityManager->flush();
The systemUser is persisted to the database after every flush().
I already tried to get only the reference with
$this->systemUser = $this->entityManager->getReference(User::class, $this->systemUser->getId());
But this doesn't work either.
Edit:
The setCreatedBy-Method:
public function setCreatedBy(User $user): void {
$this->createdBy = $user;
}
The 'createdBy'-Field:
/**
* #var User
* #ORM\ManyToOne(targetEntity="App\Entity\User", cascade={"persist"})
* #ORM\JoinColumn(nullable=true)
*/
protected $createdBy;
You have configured the cascade={"persist"} option for the relation which means Doctrine will save "new" entities found through the relation.
As your question suggests the createdBy field can only contain a relationship to already existing users. This means cascade is definitely not necessary here. It's also the reason why existing entities are persisted again in your case.
Remove the cascade option from the mapping-configuration for the property to resolve your issue.
Clear your cache afterwards.
To answer my own question:
I forgot to mention that I insert ~ 70.000 items in a loop and use clear() every 100 rows. The problem is the use of the entity manager's clear()-method. it detaches all doctrine-managed entites so doctrine thinks that these entities are new and saves them to the db. So I have to do it like this:
$newUser->setCreatedBy($this->em->getReference(User::class, $this->systemUser->getId()));
I have been looking around for a clean solution on how to update (keep in sync) a many to many relationship?
I have the following scenario:
A Sprint Entity owns the Many To Many relationship towards the Ticket entity.
When editing a Ticket (or Sprint, but I am not there yet), I want to be able to select (checkboxes) the Sprints that this ticket belongs to.
Upon persistance (save), I want to update my join table tickets_sprint (which is just a join table on ticket_id, sprint_id).
Adding Sprints to the Ticket seems easy enough, but removing Sprints from the Ticket is not reflected at all.
Code
Ticket Entity contains this method for adding a Ticket to a Sprint:
public function setSprints($sprints) {
/**
* #var $sprint \AppBundle\Entity\Sprint
*/
foreach ($sprints as $sprint) {
$this->sprints[] = $sprint;
$sprint->addTicket($this);
}
}
I have read here that the only way to go would be to remove all relations and re-save them upon persistance.
Coming from the Laravel world, this hardly feels like a good idea :)
This is how it is done in Laravel:
/**
* #param \App\User $user
* #param \App\Http\Requests\StoreUserRequest $request
* #return \Illuminate\Http\RedirectResponse
* Update the specified resource in storage.
*/
public function update(User $user, StoreUserRequest $request)
{
$user->fill($request->input());
$user->employee_code = strtolower($user->employee_code);
$user->roles()->sync($request->role ? : []);
$user->save();
\Session::flash('flash_message_success', 'The user was successfully updated.');
return redirect()->route('frontend::users.show', [$user]);
}
All suggestions are welcome!
The EntityType that you may use to create a multiple selectbox doesn't have a by_reference option like CollectionType.
If your Ticket Entity use the "inversedBy" side, you don't need to add the reference in the other object. So you can symply do this :
public function setSprints($sprints) {
$this->sprints = $sprints;
}
Maybe this will be enough to add and remove your elements automatically (Sorry didn't try).
Otherwise you have to do it manually and you can create a new method to remove elements returns by the difference between your new ArrayCollection and the old one.
given a user and his coupons, I want to get a user and all of his coupons:
foreach ($this->createQueryBuilder('x')->select('u, c')->where('x.email = ?0')->setParameter(0, $email)->leftJoin('u.coupons', 'c')->getQuery()->getResult() as $entity)
{
$entity->getCoupons();
}
this is very good until I forget to join the coupons:
foreach ($this->createQueryBuilder('x')->select('u')->where('x.email = ?0')->setParameter(0, $email)->getQuery()->getResult() as $entity)
{
$entity->getCoupons();
}
sadly this still works even though no coupons were joined. Here it does an other SELECT. In additional, this 2nd select will be wrong. Id rather want to get a exception or AT LEAST an empty array instead. Is there any workaround for this?
What you're experiencing is expected doctrine behavior.
When you select a User entity, Doctrine will get the record from the database. If you aren't explicitly joining the Coupon entity (or any other entities with relationship to User), Doctrine will create a Proxy object. Once you access this proxy object by calling $user->getCoupons(), Doctrine will fire a new query to the database to get the coupons for your User entity. This is called lazy-loading.
I'm not sure if there is a way to change this in the way you described.
What you can do is to create a method in your UserRepository called findUserAndCoupons($email) and have your query there. Whenever you need to find a user and his coupons, you could simply retrieve it in your controller using:
class MyController extends Controller {
public function myAction(){
$user = $this->getDoctrine()->getRepository('UserRepository')->findUserAndCoupons($email);
foreach($user->getCoupons() as $coupon) {
// ....
}
}
}
This way you won't need to remember the actual query and copy/paste it all over the place. :)
I have a Symfony project that is using the DoctrineMigrations bundle, and I have a really simple question: When I run a migration (e.g., when I'm pushing an update to production), how can I insert data to the database?
For example: I have an Entity which is the type of an add. The entity is:
private $addType; // String
private $type1; // Boolean
private $type2; // Boolean
private $type3; // Boolean
I add another field ($type4), and I want to add a new record to the database, with this values:
$addType = 'Type number 4';
$type1 = false;
$type2 = false;
$type3 = false;
$type4 = true;
How can this be done with DoctrineMigrations? Is it possible?
Using the Entity Manager as suggested in another answer is not a good idea, as it leads to troubles later.
In the first migration, I created a table with users and populated some users via $em->persist($user); which seemed fine at the beginning.
But after a month, I added a phone column to my User model. And Doctrine generates INSERT statements with this column within the first migration, which fails due to the non-existing column phone. Of course it doesn't exist yet in the first migration. So it is better to go with pure SQL INSERTs.
I just asked a related related question.
It is possible to use the migrations bundle to add data to the database. If you add a new property and use the doctrine mapping then the
php app/console doctrine:migrations:diff
command will generate a new migration file. You can just put your insert statements inside this file using the syntax:
$this->addSql('INSERT INTO your_table (name) VALUES ("foo")');
Make sure you put it after the auto-generated schema changes though. If you want to separate your schema changes and your data changes then you can use
php app/console doctrine:migrations:generate
to create an empty migrations file to put your insert statements in.
Like I said in my related question, this is one way to do it, but it requires manually creating these if you want to change this data in the database.
Edit:
Since this answer seems to get a few views I think it's worth adding that to more clearly separate the data changes from the schema changes there is a postUp method that can be overridden and that will be called after the up method.
https://www.doctrine-project.org/projects/doctrine-migrations/en/3.0/reference/migration-classes.html#postup
I've "found" the correct way to solve my problem (insert data after running migrations, using my entity classes).
Here is: https://stackoverflow.com/a/25960400
The idea is to declare the migration as ContainerAware, and then, from the postUp function, call the DI to get the EntityManager. It's really easy, and you can use all your entities and repositories.
// ...
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class Version20130326212938 extends AbstractMigration implements ContainerAwareInterface
{
private $container;
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
public function up(Schema $schema)
{
// ... migration content
}
public function postUp(Schema $schema)
{
$em = $this->container->get('doctrine.orm.entity_manager');
// ... update the entities
}
}
when you make the new field you need to enter this annotation "options={"default":1}" and it should work.
/**
* #var boolean
* #ORM\Column(name="type4", type="boolean", options={"default":1})
*/
private $type4 = true;
Took me some time to figure this out :)
It does, if you know how to format the array;
$this->connection->insert('user', ['id' => 1, 'gender' => 'Male']);
this is good solution for me. Just use bin/console make:migration and when migration is generated just edit if and add "DEFAULT TRUE":
$this->addSql('ALTER TABLE event ADD active TINYINT(1) NOT NULL DEFAULT TRUE');
It doesn't sound a good idea to fill date in migration, not its responsibility, symfony has a way of doing that. https://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html
I detected this problem "thanks" to an exception I got:
Catchable Fatal Error: Argument 3 passed to
Doctrine\ORM\Event\PreUpdateEventArgs::__construct()
must be an array, null given, called in
/.../vendor/doctrine/lib/Doctrine/ORM/UnitOfWork.php on line 804
and defined in
/.../vendor/doctrine/lib/Doctrine/ORM/Event/PreUpdateEventArgs.php line 28
I am working on a project that requieres a specific logic:
When the order field in entity book is modified, I need to update field books_order_modified_at in the parent entity bookstore (this field allows me to know whether the order of books in a bookstore was changed).
I decided to do it in an event listener since there are many places in the code that might change the order of books.
I didn't find any way to update a related entity from preUpdate event, so I have a private field in the listener class which I use to tell the postUpdate event to update the relevant bookstore entity.
My problem is that when I do so the preUpdate event of the book entity is fired.
When I check the change-set it contains only the modified_at field, but it has the same value before and after.
If someone has another idea how to solve the problem - great.
If not - any idea how can I prevent the preUpdate event from being fired when the flush is called in teh postUpdate event??
Actually, this is a problem from doctrine Doctrine Issue DDC-2726. Solved it by adding a clear call on the entity manager after the flush in the listener so the 3-rd argument to that constructor, which is actually the entityChangeSets, will be re-written.
What about updating the modified_at within your entities and let doctrine handle it? You would change your setOrder method in your book to update the BookOrder entity like this:
class Book {
public function setOrder($order) {
// modify book
$this->bookOrder->updateModifiedAt();
}
}
Of course your BookOrder would have to implement modifiedAt:
class BookOrder {
public function updateModifiedAt() {
$this->modifiedAt = new \DateTime();
}
}
If you use other classes for your Datetime, you of course have to change this code!
Doctrine should recognize that BookOrder has changed and should update it without any need to use a event listener.
I can suggest you to use Timestampable extension for Doctrine from DoctrineExtensionsBundle.
By using it you don't need to set created_at or modified_at values. This extension does it automatically. Even it can set modified_at only when specific fields were modified. See example.
I think you are writing something like this extension. So, you don't need to do that because this is already done :)
I had a similar problem to this. Trying to use preupdate to modify child elements caused the same error. In the end, my solution to simply update the children belonging to the parent. No explicit call to flush required.
/**
* Update expiry dates for all runners belonging to a campaign
*
* #param $runners
* #param $expiryDate
*/
private function updateCampaignRunners($runners, $expiryDate){
foreach($runners as $runner){
$runner->setExpiresAt($expiryDate);
$this->getModelManager()->update($runner);
}
}
/**
* Post update and persist lifecycle callback
*
* #param Campaign $campaign
*/
private function postAction(Campaign $campaign)
{
$runnerExpire = $this->getForm()->get("runnerExpire")->getData();
if($runnerExpiryDate && $campaign->getRunners()){
$this->updateCampaignRunners($campaign->getRunners(), $runnersExpiryDate);
}
}