Doctrine is trying to set default values even when none is defined - symfony

I am currently migrating from a legacy project to a Symfony4.
So I still need to keep the schema of the database.
I imported the database, and after fixing tons of issues I'm still unable to solve this one :
I have a join column where Doctrine is trying to set default value
$this->addSql('ALTER TABLE temperature_recording_system_sensor ALTER temperature_recording_system_site_uuid SET DEFAULT \'uuid_generate_v4()\'');
My column definition is actually like this :
/**
* #var TemperatureRecordingSystemSite
*
* #ORM\ManyToOne(targetEntity="TemperatureRecordingSystemSite")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="temperature_recording_system_site_uuid", referencedColumnName="temperature_recording_system_site_uuid", nullable=true, columnDefinition="DEFAULT NULL")
* })
*/
private $temperatureRecordingSystemSiteUuid;
How should I tell Doctrine to not set default values, as I can't use the option field on join column ?
I'm on Postgres 9.6.10 also.
Ia not trying to set a default value, plus it's on a JoinColumn, not a Column.

Related

On delete of Parent entities - null the association on the other entity

I am trying to solve my issue on Doctrine ORM. I have 2 parent entities: CompanyDoctrineEntity and ServiceDoctrineEntity and 1 entity that are associated with these 2 (but the association is not required) OrderLinkRedirectLogDoctrineEntity. The association in OrderLinkRedirectLogDoctrineEntity is defined by:
class OrderLinkRedirectLogDoctrineEntity {
/**
* #Id
* #Column(type="integer")
* #ORM\GeneratedValue()
*
* #var int $id
*/
private $id;
/**
* Many logs have one company. This is the owning side.
*
* #ManyToOne(targetEntity="CompanyDoctrineEntity", cascade="detach")
* #JoinColumn(name="company_id", referencedColumnName="id")
*
* #var CompanyDoctrineEntity $company
*/
private $company;
/**
* Many logs have one service. This is the owning side.
*
* #ManyToOne(targetEntity="ServiceDoctrineEntity", cascade="detach")
* #JoinColumn(name="service_id", referencedColumnName="id")
*
* #var ServiceDoctrineEntity $service
*/
private $service;
}
My expected behaviour is, whenever either CompanyDoctrineEntity or ServiceDoctrineEntity is removed from the database, the association in the OrderLinkRedirectLogDoctrineEntity will be NULLed, which I believe what the cascade="detach" does, but for some reason, it's not working, as I am getting the following errors:
Fatal error: Uncaught PDOException: SQLSTATE[23000]: Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails (`test_app2`.`logs_order_link_redirects`, CONSTRAINT `FK_6C1CA74CED5CA9E6` FOREIGN KEY (`service_id`) REFERENCES `app_services` (`id`)) in /Users/arvil/Projects/app2.test/public_html/wp-content/themes/app-theme/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatement.php:117
Stack trace:
#0 /Users/arvil/Projects/app2.test/public_html/wp-content/themes/app-theme/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatement.php(117): PDOStatement->execute(NULL)
#1 /Users/arvil/Projects/app2.test/public_html/wp-content/themes/app-theme/vendor/doctrine/dbal/lib/Doctrine/DBAL/Connection.php(1054): Doctrine\DBAL\Driver\PDOStatement->execute()
#2 /Users/arvil/Projects/app2.test/public_html/wp-content/themes/app-theme/vendor/doctrine/dbal/lib/Doctrine/DBAL/Connection.php(656): Doctrine\DBAL\Connection->exe in /Users/arvil/Projects/app2.test/public_html/wp-content/themes/app-theme/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractMySQLDriver.php on line 49
I'm far from an expert on Doctrine, so take this with a grain of salt and test thoroughly.
Your relations are not nullable (defaults to false), which is why your foreign key constraint is complaining: logs_order_link_redirects.service_id (and company_id) isn't allowed to be null. That likely wasn't a problem before because you're not inserting the OrderLinkRedirectLogDoctrineEntity entities without the relationships. If you were to say
$redirectLog = new OrderLinkRedirectLogDoctrineEntity();
$entityManager->persist($redirectLog);
$entityManager->flush();
you'd probably trigger the same error immediately.
Also, I don't believe you want cascade={"detach"} here. Detach would just remove the entity from this entity manager instance (in other words: for the running process), so anything you'd do to the entity after detaching it wouldn't be reflected in the database when $entityManager->flush() is called. On the next request, the entity would be back in the entity manager.
I believe that adding nullable=true to your ManyToOne's JoinColumn annotations, e.g.
#JoinColumn(name="company_id", referencedColumnName="id", nullable=true)
will get you the result you're looking for. You'll need to update your database schema afterwards for changes to be applied to the tables. Also, make sure you don't have (or add) orphanRemoval=true on the inverse side so Doctrine doesn't automatically remove your entities if they lose their parent.
I prefer adding the JoinColumn annotation to relationships as well, even though it's not required if you're fine with Doctrine's default field name choices. Adding nullable=false makes it more explicit that this relationship cannot be null. That's implied if you don't have nullable=true, but when I start looking at relationships and need to know whether they can be null or not, I'm usually confused by something and I don't have mental energy to spare to actively remember the default values for important attributes.

Doctrine: set foreign relation

This is regarding a problem with Doctrine when I try to insert a record into a associative entity. Below is a simplified description of the problem.
I have two tables, let's call them One and Two. Table One has a foreign key to table Two, called twoId with a column two_id. Field two_id happens to be part of the primary key.
* #ORM\Id
* #ORM\Column(name="user_id", type="string", length=40)
*/
private $twoId;
/**
* #ManyToOne(targetEntity="[...]", inversedBy="[...]", fetch="EAGER")
* #JoinColumn(name="two_id", referencedColumnName="id", onDelete="CASCADE")
*/
private $two;
I am trying to insert a new record into table A. This works:
$two = [.. read from DB ..];
$one = new One();
$one->setTwo($two);
$one->setTwoId($two->getId());
$em->persist($one);
$em->flush();
I don't like to call both setTwo and setTwoId. Furthermore, I don't like reading the $two record before referencing it.
If I skip setTwoId call, I get the error: Entity of type [..] is missing an assigned ID for field 'twoId'. The identifier generation strategy for this entity requires the ID field to be populated before EntityManager#persist() is called.
If I skip setTwo call, I get the error: Integrity constraint violation: 1048 Column 'two_id' cannot be null
My problems are:
How can I avoid calling both setTwo() and setTwoId()?
What if I want to reference a entity from Two without reading it? Should I use $em->getReference()? (PhpStorm doesn't even recognize it)
In case someone makes the same mistake:
As pointed out by #lordrhodos, declaring the field $twoId was wrong because Doctrine will create it automatically without having a definition.
Definition:
/**
* #ManyToOne(targetEntity="[...]", inversedBy="[...]", fetch="EAGER")
* #JoinColumn(name="two_id", referencedColumnName="id", onDelete="CASCADE")
*/
private $two;
Usage:
$two = [.. read from DB ..];
$one = new One();
$one->setTwo($two);
$em->persist($one);
$em->flush();

What is the difference b/w setting default value by the options attribute and setting it through the variable directly?

If you want to know how to set default value in symfony2, look here.
I can set it through both ways. For example, I can set it through the variable directly like this.
/**
* #var string $variable
*
* #ORM\Column(name="variable", type="string", nullable=true)
*/
private $variable = "default_value";
or i can use the options attribute
/**
* #var string $variable
*
* #ORM\Column(name="variable", type="string", nullable=true,options={"default" = "default_value"})
*/
private $variable = "default_value";
I want to know what is the difference b/w each of these two. More importantly what are the cases when the first way won't suffice , and options attribute has to be used.
From what i've come to know so far, setting the variable directly sets default value on a symfony2 level , and the options attribute sets it for doctrine. What difference does it make to set default value on ORM level, and when does it clash with symfony2 defaults? What happens if i only use one of the two.
You're right, the difference is that the value is being set on different levels. To elaborate, if you create a default value using:
/**
* #var string $variable
*
* #ORM\Column(name="variable", type="string", nullable=true)
*/
private $variable = "default_value";
When do you create a new object with the default value.
$a = new MyClass();
$a->getVariable(); // -> 'default_value'
So the actual default value is accessible immediately. If you use the second approach:
/**
* #var string $variable
*
* #ORM\Column(name="variable", type="string", nullable=true,options={"default" = "default_value"})
*/
private $variable;
This will cause that the schema in DB will contain default value for the column so the value will be accessible after you save the entity
$a = new MyClass();
$a->getVariable(); // -> null
$em->perist($a);
$em->flush();
$a->getVariable(); // -> 'default_value'
I would say this is the basic difference. It really depends of where you want to control your default values and when you want to have them.
In my opinion, in most of the cases the assignment in the entity itself is better, since if you want to change the value later in the system, all you need to do is update the value on the entity.
If you will be using the default in the database, then updating to a different value will need to update the annotation and then create a migration to alter the default value in the DB field
UPDATE (Migration question):
In case you use default value specified in Doctrine annotation and you're using Doctrine migrations bundle for the Symfony project and you will write your migration for this class manually (not by using php app/console doctrine:migrations:diff), you need to specify manually the default as an option. The annotation itself just tells how the column definition should look like.
If the default value is set only in PHP, i.e. private $variable = 'default_value'; migration bundle will not be affected by it at all. That means that if you run php app/console doctrine:migrations:diff the DEFAULT value for the field won't be filled in. Of course if you write the migration yourself, the default value in the database will be there only if you fill it automatically in.
To sum the migration budle functionality - the automatically generated migrations are affected only by default value in #ORM\Column annotation.

Proper migration path to add a unique token column in existing entity for symfony2/doctrine

I would like to add a new unique token column to an existing customer entity.
I know I need to change the customer class, update the schema, create unique token for each customer.
This is easy in dev as I just wipe the data from the database and start new. But I can't do this on the production database.
So, What is the proper or appropriate method to make this change?
Here are my changes to Customer.php:
/**
* Customer
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="AppBundle\Entity\CustomerRepository")
* #UniqueEntity("urlToken")
*/
class Customer
{
...
/**
* #var string
*
* #ORM\Column(name="urlToken", type="string", unique=true)
*/
private $urlToken;
The proper way to deploy schema changes on production is to use Doctrine Migrations.
1) Modify the entity class
2) Generate doctrine migrations:
$ ./doctrine migrations:diff
3) Update doctrine proxy classes if necessary:
$ ./doctrine orm:generate:proxies
4) Execute migrations on production:
$ ./doctrine migrations:migrate

Doctrine Associations Mapping

I'm trying Doctrine Associations with Symfony2 for the first time and it's giving me headache.
I have an Admin interface that can, among other things, upload images. I want to know which administrator uploaded what image so i've putted a foreign key administrator to my images table. To gather data, a simple JOIN is necessary to collect the data but with Doctrine, I'm stuck, altough it seems simple.
So, I have an Administrator object that reflects the table. In that object, I have this statement...
#ORM\OneToMany(targetEntity="ImageBundleNamespace\ImageEntity", mappedBy="administrator")
It's simple. In my ImageEntity object (that reflects Images table) is a foreigh key column administrator.
In the ImageEntity object, I use this statement...
#ORM\ManyToOne(targetEntity="AdministratorNamespace\Administrator", inversedBy="imageEntity")
#ORM\JoinColumn(name="administrator", referencedColumnName="id")
There is a administrator field in ImageEntity and a imageEntity field in Administrator that the formentioned statements are mapping.
It doesn't work.
I've run the SchemaValidator on the EntityManger and it says the the administrator field on the ImageEntity object is not defined as an association. The second message says that the administrator field does not exist.
If it helps, this is my DQL for all of it...
'SELECT i.id,
i.imeSlike,
i.velicina,
i.ekstenzija,
i.paths,
a.username,
a.ime,
a.prezime FROM ImageBundle:ImageEntity i
JOIN a.administrator a'
Thank you in advance for all the help.
EDIT
I had a mistake in DQL. Corrected it.
EDIT
I forgot to add the source code.
Association part of the Administrator...
**
* #ORM\OneToMany(targetEntity="Icoo\Administracija\GalerijaBundle\Entity\ImageEntity", mappedBy="administrator")
*/
protected $imageEntity;
public function __construct() {
$this->imageEntity = new ArrayCollection();
}
Association part of the ImageEntity
/**
* #ORM\Column(type="smallint")
*
* #ORM\ManyToOne(targetEntity="Icoo\LoginBundle\Entity\Administrator", inversedBy="imageEntity")
* #ORM\JoinColumn(name="administrator", referencedColumnName="id")
*
*/
protected $administrator;
In the administrator class, you have:
/**
* #ORM\Column(type="smallint")
*
* #ORM\ManyToOne(targetEntity="Icoo\LoginBundle\Entity\Administrator", inversedBy="imageEntity")
* #ORM\JoinColumn(name="administrator", referencedColumnName="id")
*
*/
you need to remove the #ORM\Column(type="smallint")
This must be all. Let me know.
You can separate field mapping from association mapping:
/**
* #ORM\Column(name="administrator", type="smallint", nullable=false, options={"unsigned"=true})
*/
protected $administratorId;
/**
* #ORM\ManyToOne(targetEntity="Icoo\LoginBundle\Entity\Administrator", inversedBy="imageEntity")
* #ORM\JoinColumn(name="administrator", referencedColumnName="id")
**/
protected $administrator;
If you go further you can put exception into $administratorId getter/setter to avoid usage of it.
As i have tested, doctrine ignores value into $administratorId property when you persist/flush your entity (For confirmation you can look at prepareUpdateData() in Doctrine\ORM\Persisters\BasicEntityPersister)
EDIT
I think, my previous variant is possible but wrong. Because, doctrine gets field mapping from definitions in referencedColumn, you can add some more definitions using
* #ORM\JoinColumn(name="administrator", referencedColumnName="id", unique, nullable, onDelete, columnDefinition, fieldName)
This means that your image_entity.administrator field in db will be the same as your administrator.id field in db (except of additional definitions)

Resources