Add data when running Symfony migrations - symfony

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

Related

What is the best way to create a singleton entity in Symfony 4?

I want to create a settings page, which only has a form in it. If the form is submitted it only updates settings entity but never creates another one. Currently, I achieved this like:
/**
* #param SettingsRepository $settingsRepository
* #return Settings
*/
public function getEntity(SettingsRepository $settingsRepository): Settings
{
$settings = $settingsRepository->find(1);
if($settings == null)
{
$settings = new Settings();
}
return $settings;
}
In SettingsController I call getEntity() method which returns new Settings entity (if the setting were not set yet) or already existing Settings entity (if setting were set at least once).
However my solution is quite ugly and it has hardcoded entity id "1", so I'm looking for a better solution.
Settings controller:
public function index(
Request $request,
SettingsRepository $settingsRepository,
FlashBagInterface $flashBag,
TranslatorInterface $translator,
SettingsService $settingsService
): Response
{
// getEntity() method above
$settings = $settingsService->getEntity($settingsRepository);
$settingsForm = $this->createForm(SettingsType::class, $settings);
$settingsForm->handleRequest($request);
if ($settingsForm->isSubmitted() && $settingsForm->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($settings);
$em->flush();
return $this->redirectToRoute('app_admin_settings_index');
}
return $this->render(
'admin/settings/index.html.twig',
[
'settings_form' => $settingsForm->createView(),
]
);
}
You could use Doctrine Embeddables here.
Settings, strictly speaking, should not be mapped to entities, since they are not identifiable, nor meant to be. That is, of course, a matter of debate. Really, a Settings object is more of a value object than an entity. Read here for more info.
So, in cases like these better than having a one to one relationship and all that fuzz, you probably will be fine with a simple Value Object called settings, that will be mapped to the database as a Doctrine Embeddable.
You can make this object a singleton by creating instances of it only in factory methods, making the constructor private, preventing cloning and all that. Usually, it is enough only making it immutable, meaning, no behavior can alter it's state. If you need to mutate it, then the method responsible for that should create a new instance of it.
You can have a a method like this Settings::createFromArray() and antoher called Settings::createDefaults() that you will use when you new up an entity: always default config.
Then, the setSettings method on your entity receieves only a settings object as an argument.
If you don't like inmutablity, you can also make setter methods for the Settings object.

Doctrine 2, prevent getting unjoined entities

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. :)

How to make work soft deletable and unique entity using symfony 2

I've soft deletable and a uniqueentity field. It works great but...
If the record is deleted "softdeleted", I can't create the same record. I think it's because the record is not realy deleted in the DB. But I need to that.
So what is the best way to dothis ?
Totaly deleted the record ? So is softdeletable a good choice ?
Find a way that if the record is softdeleted, I can create again the same record
Thanks for your advices
After you removed the unique constraint from the database level, You can set to your entity this.
#UniqueEntity(fields={"name", "deleteTime"}, ignoreNull=false)
In this case the validation will fail if you already have a "non-soft deleted" row with the given name in your database, but it won't if the deleteTime is setted.
since you are using soft delete and unique constraints, you can't actually use a unique constraint on the database level.
I suggest you handle the unique constraint check manually, this could be done in a doctrine life cycle event
One way to do this is by creating a callback function in your entity and annotate it to fire on the event:
/** #PrePersist */
public function prePersist(LifecycleEventArgs $args)
{
$entity = $args->getObject();
$entityManager = $args->getObjectManager();
// check if this entity's unique field is OK
}
This will only ensure you don't save anything incorrect in the database, but it won't handle your forms nicely. So in addition, you probably want to use the UniqueEntity validator for this, and create a custom repositoryMethod to check the uniqueness.
This custom repository method can be used by both the prePersist and the UniqueEntity validator.
You have three choices
Hard Delete the item
Remove the Unique (and handle it in doctrine)
When you create the new entity, you deactivate the softdeletable filter
$em->getFilters()->disable('soft-deleteable');
This will let you find the "deleted" items. Then you can do things like overwrite the old entry, harddelete it manually or whatever your app needs you to do with it.
In my case, I used this way
Remove the unique index of the column on the Database
public function up(Schema $schema) : void
{
$this->addSql('DROP INDEX UNIQ_A2E0150FE7927C74 ON admins');
$this->addSql('CREATE UNIQUE INDEX UNIQ_A2E0150FE7927333 ON
admins (email,deleted_at)');
}
Add this constraint on your Entity
/**
* #ORM\Entity(repositoryClass=AdminRepository::class)
* #ORM\Table(name="admins",
* uniqueConstraints={
* #UniqueConstraint(name="admins",
* columns={"email", "deleted_at"})
* })
It means that you make the pair email (unique column) and deleted_at unique, instead of just the email field. And now, I can create another admin with the same email, if the old one was deleted (Using soft delete)

One-to-many relationship: how to set default entity-value?

I have a one-to-many relationship Cart-SendingMethod. I would like to set a default SendingMethod for new Carts. So I have tried this:
<?php
/**
* #ORM\ManyToOne(targetEntity="MetodoEnvio", inversedBy="metodoEnvios")
* #ORM\JoinColumn(name="metodo_envio_id", referencedColumnName="id")
**/
private $metodoEnvio = 1;
but doesn't work... I get:
Impossible to access an attribute ("id") on a integer variable ("1") when I call Cart.SendingMethod.id from a view file
So how to set a default SendingMethod for new Products?
I could do it in the controller, but I would like to know if it is possible from the entity Product.
Note: I didn't know exactly if this is a symfony or doctrine question.
You don't want to introduce dependencies into your entity.
The obvious and cleaner way to do it would be to create a CartFactory service, and inject that into any controller (or other class) that needs to create carts. Inject your EntityManager and other dependencies into the factory. That way you DRY up your cart-initialization code, and avoid bulking up your controller.
Just set the property's default value inside the constructor like this:
public function __construct(..)
{
$this->property = new OtherObject();
}

Access private or protected properties from Symfony2 repository

I'm trying to access private properties of an entity from his own repository. By doing PHP tells me I can not access private or protected properties or methods.
I'm just calling a custom repository method created by me, passing an instance of the entity. When i try to get the ID for example, php throws me the error.
How I can access it?
public function customMethod($entityInstance)
{
$query = $this->getEntityManager()
->createQuery(
'SELECT c
FROM AcmeMainBundle:Content c
WHERE
c.published = 1
AND
c.id != :id
ORDER BY c.date DESC'
)
->setParameter('id',$entityInstance->id);
return $query->getResult();
}
This is an example of the custom method of my repository. Obviously is just an example, that DQL is not very usefull but when I try to access to $entityInstance->id ...
Can someone helps me?
The relationship between a Repository and an Entity is conceptual, not structural. This means that although you know they are related, PHP doesn't.
If you want to access private members of the Entity from the Repository you will have to do it like with any other class: using getters and setters.
Try this:
->setParameter('id', $entityInstance->getId());
Well, as with all normal PHP scripts, if you want to access a private or protected property you have to create a getter. DQL isn't changing anything from the PHP site, it only adds minor changes to the SQL syntax (which is just a string in PHP).
So actually, I don't see what you're trying to say with this question. That DQL should change the way PHP and OO works?

Resources