In an application I have a case of the Class Table Inheritance. The discriminator column is an ENUM:
/**
* Foo
*
* #ORM\Table(name="foos", ...)
* #ORM\Entity
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="`type`", type="string", columnDefinition="ENUM('bar', 'buz')")
* #ORM\DiscriminatorMap({
* "bar" = "Bar",
* "buz" = "Buz"
* })
*/
abstract class Foo
{
...
}
Doctrine works as expected (to begin with). The doctrine:migrations:diff command creates a migration for the tables and relationships and also defines the discriminator column correctly, as an ENUM.
Then I execute the migrations (doctrine:migrations:migrate). The schema looks well. But:
When I execute the diff command again (and expect no new migrations), I get a new migration generated:
final class Version20180619205625 extends AbstractMigration
{
public function up(Schema $schema) : void
{
$this->addSql('ALTER TABLE foos CHANGE type `type` ENUM(\'bar\', \'buz\')');
}
public function down(Schema $schema) : void
{
$this->addSql('ALTER TABLE tasks CHANGE `type` type VARCHAR(255) DEFAULT NULL COLLATE utf8mb4_unicode_ci');
}
}
Alright, I execute it. And try the diff command again. And get the same migration generated again... So, Doctrine seems to "think", the column is still VARCHAR.
I showed the issue here on example of an inheritance discriminator. But actually it doesn't matter, if the column is a discriminator or not -- this behavior is the same for every ENUM column.
How to solve this issue? Is there a way make Doctrine handle ENUM columns correctly?
Related
In Laravel 6, I have a table companies and I want to change the column card to nullable.
I created a new migration and I can change it using
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::table('companies', function (Blueprint $table) {
$table->double('card')->nullable()->change();
});
}
But the problem is doctrine/dbal only supports some specific datatypes to update.
Error: Unknown column type "double" requested. Any Doctrine type that
you use has to be registered with
\Doctrine\DBAL\Types\Type::addType(). You can get a list of all the
known types with \Doctrine\DBAL\Types\Type::getTypesMap().
Is there any way I can update this column to nullable without touching its datatype? or any DB statement?
I can use
DB::statement('ALTER TABLE `companies` CHANGE `card` `card` INT NULL DEFAULT NULL;');
but I am concern is this right way to do?
according to Laravel doc:
The following column types can be modified: bigInteger, binary,
boolean, date, dateTime, dateTimeTz, decimal, integer, json,
longText, mediumText, smallInteger, string, text, time,
unsignedBigInteger, unsignedInteger, unsignedSmallInteger, and uuid.
so if your column is not from those types it will not be able to change
I have two float fields: fieldA and fieldB. I would like to store in DB float field fieldC, which will always have value: fieldA * fieldB.
I would like to achieve that with migrations. This is what I tried
$table->float('fieldC', 12, 2)->storedAs('fieldA * fieldB');
and
$table->float('fieldC', 12, 2)->storedAs('MULTIPLY(fieldA, fieldB)');
Both didn't work. Errors are Column not found: 1054 Unknown column 'fieldA' and General error: 1901 Function or expression 'MULTIPLY()' cannot be used.
Any suggestions?
Laravel migrations dosn't support that. But you can make trigger with raw statement.
Something like that:
DB::unprepared("
DELIMITER $$
CREATE TRIGGER after_update
AFTER UPDATE
ON tableName FOR EACH ROW
BEGIN
IF old.fieldA <> new.fieldA OR old.fieldB <> new.fieldB THEN
UPDATE tableName SET fieldC=fieldA+fieldB WHERE id=new.id;
END IF;
END$$
DELIMITER ;
");
You can make it more simple using Laravel model
<?php
class YourModel extends Model {
public static function boot()
{
parent::boot();
self::saving(function($model){
$model->fieldC = $model->fieldA + $model->fieldB;
});
}
}
I am saving some values coming from a form (managed by CodeIgniter) into simple Doctrine entities. Some of my fields are optional.
Sample code of controller :
$entity->setDistance($this->input->post('distance'));
$entity->setKilometricRate($this->input->post('kilometric_rate'));
$this->getEntityManager()->persist($entity);
$this->getEntityManager()->flush($entity);
When I don't fill some fields in the form, then 0 is persisted into the database (since php converts "" in 0).
Sample code of entity :
/**
* #var float|null
*
* #ORM\Column(name="distance", type="float", precision=53, scale=0, nullable=true)
*/
private $distance;
/**
* #var float|null
*
* #ORM\Column(name="kilometric_rate", type="float", precision=53, scale=0, nullable=true)
*/
private $kilometricRate;
Is there a way to tell Doctrine to save NULL value instead of 0 or empty string, without testing all the strings in all the setters?
At the moment I am using a custom helper, but I am quite sure that there is a better way to do this.
Helper :
public function getValueOrNull(?string $postedVal):?string{
if ($postedVal==="0") return "0";
return (!empty($postedVal) ? $postedVal : null);
}
There are multiple places where you can tackle this. For example you could check how CodeIgniter handles forms and ensure that an empty fields returns null instead. I don't know CI well enough to say how easy it is and where this could cause side effects.
You could also deal with this in your entities making sure that whenever someone sets a value with an empty string it is converted to null:
class MyEntity
{
// ...
public function setDistance($distance)
{
if ($distance === '') {
$distance = null;
}
$this->distance = $distance;
}
}
If you want to always want to have this for floats you could create a custom type that extends float and modify the method convertToPHPValue. See https://www.doctrine-project.org/projects/doctrine-dbal/en/2.9/reference/types.html#custom-mapping-types
class Account
{
...
/*
* #OneToMany(targetEntity="Address", mappedBy="account");
*/
private $addresses;
...
}
class Address
{
...
/**
* #ManyToOne(targetEntity="Account", inversedBy="addresses")
* #JoinColumn(name="account_id", referencedColumnName="id")
*/
private $account;
...
}
When i run console command to validate schema:
[Mapping] FAIL - The entity-class 'Entity\Address' mapping is invalid:
* The association Entity\Address#account refers to the inverse side
field Entity\Account#addresses which does not exist
Why?
I think the Doctrine annotation reader uses php's Reflection API, specifically getDocComment to read the annotations. That means your doc block comments must begin with /** otherwise they will be ignored. Your $addresses property annotation begins with /* so it won't get picked up.
In mysql I can set default value for column like this:
ALTER TABLE <Table> CHANGE <Column> DEFAULT <NEW_DEFAULT_VALUE>
I can retrieve default value:
SELECT DEFAULT(<Column>) FROM <Table> LIMIT 1
Is it possible to achieve this concept with Doctrine?
What I actually need is such methods in my table class:
class UserTable extend Doctrine_Table {
/* ... */
/**
* #return Doctrine_Record
*/
public function getDefaultProperty() {
return ???;
}
public function setDefaultProperty($value) {
/* $value can be either integer or Doctrine_Record */
}
}
Let's see if we can figure this out.
Line 567 of Record.php, part of the assignDefaultValues() method which populates defaults as the object is created, says:
$default = $this->_table->getDefaultValueOf($column);
So you don't actually need to add a getDefaultProperty(), you can use:
$userTable = Doctrine::getTable('User');
$defaultName = $userTable->getDefaultValueOf('name');
However, you seem to want getDefaultProperty() to return a Doctrine_Record. If you just want to get back a whole object that has all the default values set on it, I'm pretty sure you can just use:
$defaultUser = new User(); (See the Doctrine manual for Default Values)
But be sure that your defaults are defined in the schema.yml, not just in the database. Doctrine won't read defaults out of the database, and relies on the schema to tell it what to do.
Let me know how that works.
Cheers,
~J
The Doctrine_Table class contains the getDefaultValueOf method which does what you're looking for. So, if you have a doctrine record, you can write something like: $record->getTable()->getDefaultValueOf('address_line_1'); where 'address_line_1' is a column. Hope this helps.