Default array value for a migration - php

I am trying to set a new field to my entity which is an array of booleans. I defined it like this:
/**
* #var bool[] $groupe_jours Selected days for the groups
* #ORM\Column(type="array")
*/
protected $groupe_jours;
I added the initialization in the constructor:
/** #ignore */
public function __construct()
{
$this->groupe_jours = array();
}
Now I want to make the migration
php app/console doctrine:migrations:diff
php app/console doctrine:migrations:migrate
This updates correctly the database:
ALTER TABLE licensee ADD groupe_jours LONGTEXT NOT NULL COMMENT '(DC2Type:array)'
However, when I try to reload my pages I get an error like this:
Could not convert database value "" to Doctrine Type array
This is because the array type requires a specific string when the array is empty, like: 'a:0:{}'
What is the best way to make sure that the migration updates the column correctly ?
Can I specify a default value for the migration
Can I put a default value in the field definition, that the migration should use ?
Should I update the database by hand ?

I sort of found an answer, but I am quite sure that there are better ones.
In the migration file that was created, in function public function up(Schema $schema), after:
$this->addSql('ALTER TABLE licensee ADD groupe_jours LONGTEXT NOT NULL COMMENT \'(DC2Typ
I added:
$this->addSql('UPDATE licensee SET groupe_jours = \'a:0:{}\'');
But I am quite sure that there are better solutions, as this one looks like a hack...

Related

doctrine migration: default value for not nullable fk

I have an existing entity in database. I would like to add a new column to this entity:
/**
* #ORM\ManyToOne(targetEntity="Language")
* #ORM\JoinColumn(name="language_id", referencedColumnName="id", nullable=false)
*/
protected $language;
When I now use "vendor/bin/doctrine-migrate migrations:diff", the generated migration script does not contain a default value for language_id. Therefore the migration-script fails. Setting a default value for the property in the object does not help. How can I define a default value for the fk-column? I neither found something in doctrines documentation nor through google/stackoverflow.
If the column is not null then it should represent a valid relationship; assuming you use 0 instead, Doctrine will try to load the association using that, which would of course fail. In these cases you would need to update the database and mapping to allow a null value.
If however you require a default language association to be defined then you explicitly need to set it when you create the entity.
$language = $entityManager->find(1);
$entity = new Entity;
$entity->setLanguage($language);
$entityManager->persist($entity);
$entityManager->flush();
For this reason, you might want to consider a 'service' that encapsulates the creation of your entity so you know a language will always be valid and assigned by default.
It may not be a clean way to do it, but maybe you can execute the SQL query yourself and add manually the DEFAULT statement ?
ALTER TABLE registered_user ADD language_id VARCHAR(255) NOT NULL DEFAULT {default_value} WITH VALUES;
I'm quite surprised that adding a default property in the annotation is not working here!

Disable Doctrine foreign key constraint

I have a relationship on one of my models:
/**
* #ORM\ManyToOne(targetEntity="Page", cascade="persist")
* #ORM\JoinColumn(name="page_id", referencedColumnName="id")
*/
private $parentPage;
And when I delete the parent page, I get this error:
Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails
Basically my models are a page, and page revision. When I delete the page I don't want to delete the revisions. I also want to keep the page_id on the page revisions (i.e. not set it to null).
How can I do this with Doctrine?
By definition you cannot delete the record that the foreign key is pointing at without setting the key to null (onDelete="SET NULL") or cascading the delete operation (There are two options - ORM Level: cascade={"remove"} | database level: onDelete="CASCADE"). There is the alternative of setting a default value of a still existing record, but you have to do that manually, I don't think Doctrine supports this "out-of-the-box" (please correct me if I am wrong, but in this case setting a default value is not desired anyway).
This strictness is reflecting the concept of having foreign key constraints; like #Théo said:
a FK is to ensure data consistency.
Soft delete (already mentioned) is one solution, but what you could also do is add an additional removed_page_id column that you sync with the page_id just before you delete it in a preRemove event handler (life cycle callback). Whether such information has any value I wonder but I guess you have some use for it, otherwise you wouldn't ask this question.
I am definitely not claiming this is good practice, but it is at least something that you can use for your edge case. So something in the line of:
In your Revision:
/**
* #ORM\ManyToOne(targetEntity="Page", cascade="persist")
* #ORM\JoinColumn(name="page_id", referencedColumnName="id", onDelete="SET NULL")
*/
private $parentPage;
/**
* #var int
* #ORM\Column(type="integer", name="removed_page_id", nullable=true)
*/
protected $removedPageId;
And then in your Page:
/**
* #ORM\PreRemove
*/
public function preRemovePageHandler(LifecycleEventArgs $args)
{
$entityManager = $args->getEntityManager();
$page = $args->getEntity();
$revisions = $page->getRevisions();
foreach($revisions as $revision){
$revision->setRemovedPageId($page->getId());
$entityManager->persist($revision);
}
$entityManager->flush();
}
Alternatively you could of course already set the correct $removedPageId value during construction of your Revision, then you don't even need to execute a life cycle callback on remove.
I solved this by overriding one doctrine class in symfony 4.3, it looks like this for me:
<?php declare(strict_types=1);
namespace App\DBAL;
use Doctrine\DBAL\Platforms\MySQLPlatform;
/**
* Class MySQLPlatformService
* #package App\DBAL
*/
class MySQLPlatformService extends MySQLPlatform
{
/**
* Disabling the creation of foreign keys in the database (partitioning is used)
* #return false
*/
public function supportsForeignKeyConstraints(): bool
{
return false;
}
/**
* Disabling the creation of foreign keys in the database (partitioning is used)
* #return false
*/
public function supportsForeignKeyOnUpdate(): bool
{
return false;
}
}
You can disable the exporting of foreign keys for specific models:
User:
attributes:
export: tables
columns:
Now it will only export the table definition and none of the foreign keys. You can use: none, tables, constraints, plugins, or all.
You are explicitly asking for data inconsistency, but I'm pretty sure you really don't want that. I can't think of a situation where this would be defensible. It is a bad practice and definitely will cause problems. For example: what is the expected result of $revision->getPage()?
There is a very simple and elegant solution: softdeletable. It basically adds an attribute to your entity (in other words: adds column to your table) named deletedAt to store if (or better: when) that entity is deleted. So if that attribute is null, the entity isn't deleted.
The only thing you have to do is add this bundle, add a trait to your entity (Gedmo\SoftDeleteable\Traits\SoftDeleteableEntity) and update your database. It is very simple to implement: this package will do the work for you. Read the documentation to understand this extension.
Alternatively, you can add an 'enabled' boolean attribute or a status field (for example 'published', 'draft', 'deleted').
When I delete the page I don't want to delete the revisions. I also want to keep the page_id on the page revisions (i.e. not set it to null).
I think you already got your answer: Doctrine won't do that, simply because it's alien to the notion of Foreign Keys. The principle of a FK is to ensure data consistency, so if you have a FK, it must refer to an existing ID. On delete, some DB engine such as InnoDB for MySQL allow you to put an FK to NULL (assuming you did made the FK column nullable). But referring to an inexistent ID is not doable, or it's not a FK.
If you really want to do it, don't use Doctrine for this specific case, it doesn't prevent you to use Doctrine elsewhere in your codebase. Another solution is to just drop the FK constraint manually behind or use a DB statement before your query to skip the FK checks.

Laravel Soft Delete Unique column name

Suppose I have category table and I have used soft delete on it. Now first time I have added one category "Test" after that I have delete this category so it will update my deleted_at column from the database.
Now when again I am trying to add category with name "Test" it is saying me that this name has been taken. I have tried with rules which are posted Here.
But it is not working. I have used trait in my model. Below is my model code.
<?php
namespace App\Models;
use Illuminate\Support\Facades\Validator as Validator;
use Illuminate\Database\Eloquent\SoftDeletingTrait;
class Category extends \Eloquent {
use SoftDeletingTrait;
protected $dates = ['deleted_at'];
/**
* Guarded fields which are not mass fillable
*
* #var array
*/
protected $guarded = array('id');
/**
* Name of the table used
*
* #var string
*/
protected $table = 'travel_categories';
/**
* Validating input.
*
* #param array $input
* #return mixed (boolean | array)
*/
public static function validate($input, $id = null) {
$rules = array(
'name' => array('required','min:2','max:100','regex:/[a-zA-z ]/','unique:travel_categories,name,NULL,id,deleted_at,NULL'),
'description' => 'Required|Min:2',
'image' => 'image'
);
if ($id) {
$rules['name'] = 'Required|Between:3,64|Unique:travel_categories,name,'.$id;
}
$validation = Validator::make($input,$rules);
return ($validation->passes())?true : $validation->messages();
}
}
Did you understand the soft deleting purpose? It will only flag the data to be inactive. It will be there though.
So, if you define the values of that column must be unique, it is right you could not create other one alike.
If it needs to be unique, so you should restore and update the data.
If it can have many ones, so you should remove the unique key applied on it (and call it by relationship for instance).
Look at: Laravel Eloquent Soft Deleting
First: I don't understand a couple of things. Are you trying to validate for create and update? Then why do you allow name to be of length 2 till 100 for creation, and only 3 till 64 for after updates?
Second: I recommend dropping this:
protected $dates = ['deleted_at'];
I don't see the goal of that.
Third, and I'm getting to the point here, what are you trying to do? I guess, what you are trying to do with this filter 'unique:travel_categories,name,NULL,id,deleted_at,NULL' is to check the uniqueness of the name among the active categories. In that case, that should work.
As noted above, a unique index on [category, deleted_at] will not work because when deleted_at is null, many SQL RDBMS will allow multiple records to be inserted despite a unique index existing.
In case anyone is interested I have now created a Laravel extension to handle the SQL unique-index constraints correctly: https://packagist.org/packages/tranzakt/laravel-softdeletesunique
If anyone tries this, please give feedback on Github, thanks.
I know this question is old, but I had a similar issue and I stumbled upon this. I wanted to mention how I fixed it for anyone, who is reading it in the future. The problem I had was that Laravel did not allow me to insert a value in a unique column when there was an old record with the same value, but was deleted using soft_delete.
To summarize, the goal is to ignore old soft deleted records for a unique column when inserting a new record. The solution I found is in the migration for the table. For example, let us assume we have these columns:
category - unique
deleted_at - keeps tracks of the deleted rows
Both should be specified as unique in the migration like so:
Schema::create('table_name', function (Blueprint $table) {
$table->string("category");
$table->softDeletes();
$table->unique(["category", "deleted_at"]);
});
Side note: If you already have the table like I did, you need to change the migration and create the table again (obviously the data will be lost):
Remove the table
Change the migration
Remove the record about it from the migrations table
run "php artisan migrate" to create the table again

Mongodb hash type keys return 'null' value in array

Can someone help pls? I am using doctrine_mongodb and I am trying to obtain the values of all keys in my document. I get the values of the fields with types integer, string and boolean but my fields of type hash return a value of 'null'
Here is how I define one of the hash fields in class Scripts of /documents/Scripts.php
/**
* #MongoDB\Document(collection="scripts")
*/
class Scripts
{
/**
* #MongoDB\Id
*/
public $id;
/**
* #MongoDB\Hash
*/
public $properties;
Thanks
I had the same issue. For me the Hydrator was cached with old values, and it wasn't referencing any of the new ones I added.
app/console cache:clear
app/console doctrine:mongodb:generate:documents BUNDLENAME
Solved my problem.
Fixed now. I manually created the hash type fields in "/var/www/Symfony/app/cache/dev/doctrine/odm/mongodb/Hydrators/AtlasMpBundleDocumentScriptsHydrator.php" with php type array. It is pretty easy to understand when you open the file.
Hope this helps someone :)
Functional test GH453Test.php in the ODM project includes an example of using a hash-mapped field. If you're seeing a null property after your model is hydrated, it is because the field does not exist or its value is null in the document returned from MongoDB. The conversion logic is rather straightforward in HashType::convertToPHPValue().

Doctrine2.0: Error - Column specified twice

In Doctrine2.0.6, I keep getting an error: "Column VoucherId specified twice".
The models in question are:
Basket
BasketVoucher
Voucher
Basket links to BasketVoucher.
Voucher links to BasketVoucher.
In Voucher and BasketVoucher, there is a field called VoucherId. This is defined in both models and exists with the same name in both DB tables.
The error occurs when saving a new BasketVoucher record:
$basketVoucher = new BasketVoucher;
$basketVoucher->setVoucherId($voucherId);
$basketVoucher->setBasketId($this->getBasket()->getBasketId());
$basketVoucher->setCreatedDate(new DateTime("now"));
$em->persist($basketVoucher);
$em->flush();
I've checked the models and VoucherId is not defined twice. However, it is used in a mapping. Is this why Doctrine thinks that the field is duplicated?
Here's the relevant code - I haven't pasted the models in their entirety as most of the code is get/set.
Basket
/**
* #OneToMany(targetEntity="BasketVoucher", mappedBy="basket")
* #JoinColumn(name="basketId", referencedColumnName="BasketId")
*/
private $basketVouchers;
public function getVouchers()
{
return $this->basketVouchers;
}
BasketVoucher
/**
* #ManyToOne(targetEntity="Basket", inversedBy="basketVouchers")
* #JoinColumn(name="basketId", referencedColumnName="BasketId")
*/
private $basket;
public function getBasket()
{
return $this->basket;
}
/**
* #OneToOne(targetEntity="Voucher", mappedBy="basketVoucher")
* #JoinColumn(name="voucherId", referencedColumnName="VoucherId")
*/
private $voucherEntity;
public function getVoucher()
{
return $this->voucherEntity;
}
Voucher
/**
* #OneToOne(targetEntity="BasketVoucher", inversedBy="voucherEntity")
* #JoinColumn(name="voucherId", referencedColumnName="VoucherId")
*/
private $basketVoucher;
public function getBasketVoucher()
{
return $this->basketVoucher;
}
Any ideas?
EDIT: I've found that the same issue occurs with another model when I save it for the first time. I am setting the primary key manually. The main issue appears to be saving a relationship within an entity.
In this case, I have a field - DraftOrderId - which is used as the primary key on three models. The first model - DraftOrder - has DraftOrderId as a primary key, which is an auto incrementing value. The other two models - DraftOrderDeliveryAddress, and DraftOrderBillingAddress - also use DraftOrderId as a primary key, but it isn't auto incremented.
What's happening is one of the following issues:
If I save the delivery address entity with a draft order id and set it to persist, I get an error: Column DraftOrderId specified twice. Code:
try {
$addressEntity->getDraftOrderId();
} catch (\Doctrine\ORM\EntityNotFoundException $e) {
if ($addressType == "delivery") {
$addressEntity = new Dpp\DraftOrderDeliveryAddress;
} elseif ($addressType == "billing") {
$addressEntity = new Dpp\DraftOrderBillingAddress;
}
$addressEntity->setDraftOrderId($draftOrder->getDraftOrderId());
$em->persist($addressEntity);
}
(It would also help to know if there's a better way of checking if a related entity exists, rather than trapping the exception when trying to get a value.)
If I remove the line that sets the draft order id, I get an error: Entity of type Dpp\DraftOrderDeliveryAddress is missing an assigned ID.
If I keep the line that sets the draft order id but I remove the persist line, and I also keep the lines later on in the code that sets the name and address fields, I don't get an error - but the data is not saved to the database. I am using flush() after setting all the fields - I'm just not using persist(). In the previous examples, I do use persist() - I'm just trying things out to see how this can work.
I can paste more code if it would help.
I think I've fixed it! A couple of findings:
For a primary key that is not an auto-incrementing value, you need to use:
#generatedValue(strategy="IDENTITY")
You also have to explicitly set the mapped entities when creating them for the first time. At first, I was trying to create the address entity directly, but I wasn't setting the mapped entity within the parent model to reference the address entity. (if that makes any sense)
I'm fairly sure it was mostly due to the lack of the IDENTITY keyword, which for some reason was either saying the key wasn't set, or saying it was set twice.

Categories