TINYINT wrongly converted to NULL instead of boolean in doctrine / symfony2 - php

This is the craziest thing I ever seen.
My Symfony2 and Doctrine has gone mad
I have MySQL database with few different fields that are TINYINT.
Those are not nullable and all records have those values set to 0 or 1.
All doctrine ORM mapping ale set correctly (I doublechecked it hundred times).
All getters are set correctly (doublechecked as well).
But then - for some objects it doesn't work ... some of the TINYINT are not correctly translated to BOOLEAN as it should (and as it works with other objects and with other fields of that enity)... instead it gives NULL - even if in database this TINYINT is set to "1" (or "0" - it doesn't matter).
For some other objects (of the same entity) it works fine TINYINT = "1" is correctly recognized as true and "0" as false boolean.
Those my examples:
Events.orm.xml:
<entity name="Events" table="events">
<change-tracking-policy>DEFERRED_IMPLICIT</change-tracking-policy>
<field name="eventDeleted" type="boolean" column="_event_deleted"/>
Entity: "Events.php"
the mapping:
/**
* #var boolean $eventDeleted
*
* #ORM\Column(name="_event_deleted", type="boolean", nullable=false)
*/
private $eventDeleted;
and getter and setter:
/**
* Set eventDeleted
*
* #param boolean $eventDeleted
*/
public function setEventDeleted($eventDeleted)
{
$this->eventDeleted = $eventDeleted;
}
/**
* Get eventDeleted
*
* #return boolean
*/
public function getEventDeleted()
{
return $this->eventDeleted;
}
The database is:
And as I said - for some objects it works and gives "1" or "0" when accessed by for example:
{{ event[0].getEventDeleted }}
and sometimes it gives NULL.
I found some "similar" issue mentioned in doctrine jira, but no guess what solves that and what could be the reason: http://www.doctrine-project.org/jira/browse/DDC-1967
Any idea?

Try creating the query "by hand" with the query builder.
I also had the same problem on symfony 2.1 while fetching an entity from the session variable, a boolean field was returning null no matter what value it had. Using the query builder made it work for me.
EDIT
Example:
$qb = $em->createQueryBuilder();
$query = $qb->select('e.eventDeleted')
->from('BundleName:Events', 'e')
->where('e.id = :id')
->setParameter('id', $id)
->getQuery();
$eventDeleted = $query->getSingleResult();

Related

Symonfy2, Doctrine: duplicate, default, or ignore DiscriminatorMap?

How do I enable duplicate Discriminator Maps, allow a "default" mapping, or completely ignore Discriminator mappings when running a DQL query?
Setup:
// DQL Query:
$this->createQueryBuilder('s')
->select(['s.serverId', 'p.projectName'])
->leftJoin('s.serverServices', 'ss')
->leftJoin('ss.serverServiceProjects', 'ps')
->leftJoin('ps.project', 'p')
->getQuery()
->getArrayResult()
// Mapping
/**
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="service_type_id", type="integer")
* #ORM\DiscriminatorMap({
* "1" = "AppBundle\Entity\ServerService",
* "2" = "AppBundle\Entity\ServerService",
* [...]
* "12" = "AppBundle\Entity\Service\SubService",
* "" = "AppBundle\Entity\ServerService"
* })
*/
The join portion that's generated:
LEFT JOIN server_services s2_ ON s0_.server_id = s2_.server_id AND s2_.service_type_id IN ('1', '12')
This is entirely incorrect. I have an entire mapping of 1 - 12. And an "" empty string match. If a row does not have a known mapping (in my code) OR if it's empty, it should be set to the default base ServerService.
I did find this post: Leave out discriminator part of Doctrine' generated SQL
However, it's back in 2014, made mention of a difference in version, and does not compensate for duplicate, or default Mappings.
EDIT: For the record, I tried the ignore route -- it didn't work.
$q->setHint(\Doctrine\ORM\Query::HINT_CUSTOM_OUTPUT_WALKER, MySqlWalker::class)
->setHint(MySqlWalker::IGNORE_DISCRIMINATION, array('ss'));
Still returns the above SQL Joint statement. It does call MySqlWalker; it does go into the setInheritanceType() call, but it does not ignore the Discriminator mapping. :/
Found an answer to one option: Ignoring
In Doctrine 2.2.x, my statements were in the JOIN, not WHERE clause. Using the example in Leave out discriminator part of Doctrine' generated SQL, I had to overload the ->walkJoin() method to get ignore to work properly:
/**
* {#inheritdoc}
*/
public function walkJoin($join)
{
$this->checkForHint();
return parent::walkJoin($join);
}
protected function checkForHint()
{
$ignoreDescription = $this->getQuery()->getHint(self::IGNORE_DISCRIMINATION);
if ($ignoreDescription) {
foreach ($this->getQueryComponents() as $k => $component) {
if (in_array($k, $ignoreDescription)) {
/** #var $meta ClassMetadata */
$meta = $component['metadata'];
$meta->setInheritanceType(ClassMetadata::INHERITANCE_TYPE_NONE);
}
}
}
}
Still have not resolved Duplicate or Default discriminator values...

Set doctrine entity boolean field to 0 instead of null

Im trying to persist an doctrine entity with a boolean field where the values are 0 or 1.
When the property is set to true, it save it as '1' in database.
But when its 'false' or '0', it save it as NULL on database.
How can I fix this to only save only as 1 or 0 ?
The annotation for the property I use is like following:
#ORM\Column(name="substitute", type="boolean", nullable=true)
When I set nullable to false, I cant persist it because it still want to set to null.
Thanks
When I persist it, the field value is 0
Attempt 1
#ORM\Column(name="substitute", type="boolean", options={"default":"0"}))
error: Can't save null
Attempt 2
#ORM\Column(name="substitute", type="boolean", nullable= true, options={"default":"0"}))
Doesn"t work, it still save null in base
Info 1
The actually insert query is trying to insert 0. But I got this error "ORA-01400: cannot insert NULL into (\"MYBASE\".\"MYTABLE\".\"SUBSTITUTE\")"
Info 2
Same append with another entity
class TestEntity
{
/**
* #ORM\Column(name="test_entity_id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(name="substitute", type="boolean")
*/
private $isSubstitute = false;
}
Persisting
$test = new TestEntity();
$test->setIsSubstitute(false);
$em->persist($test);
Result
request.CRITICAL: Uncaught PHP Exception Doctrine\DBAL\Exception\NotNullConstraintViolationException: "An exception occurred while executing 'INSERT INTO TestEntity (test_entity_id, substitute) VALUES (?, ?)' with params [7, 0]: SQLSTATE[HY000]: General error: 1400 OCIStmtExecute: ORA-01400: cannot insert NULL into ("MYBASE"."TESTENTITY"."SUBSTITUTE") (ext\pdo_oci\oci_statement.c:148)"\n (ext\\pdo_oci\\oci_statement.c:148) at PATH\\vendor\\doctrine\\dbal\\lib\\Doctrine\\DBAL\\Driver\\PDOStatement.php:91)"} []
Info 3
Inserting manually works using oci or oci8 driver
sql> INSERT INTO TestEntity (test_entity_id, substitute) VALUES (13, 0)
[2017-04-06 11:21:15] 1 row affected in 62ms
Just set the SQL Default to 0 (Edit: You need to update the schema after that change):
/**
* #ORM\Column(type="boolean", options={"default":"0"})
*/
protected $isActive;
Also you could initialize the property by default:
/**
* #ORM\Column(type="boolean", options={"default":"0"})
*/
protected $isActive = false;
Nullable shouldn't matter as long as the value is set to either true/false.
If you really set the property to false before saving and it still saves it as null in the DB then something else is going on.
Change your driver from oci to oci8 in your paramters.yml file:
database_driver: oci8
That should do it. Use the Underground PHP and Oracle Manual for installing OCI8.
I think #Cerad's suggestion is correct, can you try:
/**
* #ORM\Column(name="substitute", type="boolean")
*/
protected $substitute = false;
Let us know the result.
I just replicated your case, and I managed to successfully save into the db 1 for true, and 0 for false.
Example:
//Entity
Person: id, name, isMajor(boolean field)
//IMPORTANT: I setted the boolean field inside __construct() method, and let it be, by default, false (0). This means you don't need anymore to have that options={"default":"0"}.
//...
/**
* #var bool
*
* #ORM\Column(name="isMajor", type="boolean", nullable=true)
*/
private $isMajor;
public function __construct()
{
$this->isMajor = false;
}
//Created CRUD against the Entity
//When saving (either using the default actions provided by the CRUD, or by setting the values inside another controller's action):
//AppBundle/Controller/DefaultController.php
/**
* #Route("/new-person")
*/
public function createAction()
{
$person = new Person();
$person->setName('name');
$person->setIsMajor(true); // this saves 1 in the table
$person->setIsMajor(false); // this saves 0 in the table
$em = $this->getDoctrine()->getManager();
$em->persist($person);
$em->flush();
return $this->redirectToRoute('person_index');
}
I hope I did understood well your problem.
You can create fake object using $em->getReference(EntityName::class, "AnyValue");
Happy coding.

Doctrine2 join column

I have defined the follow entity in doctrine2 (with symfony).
/**
*
* #ORM\Table(name="order")
* #ORM\Entity
*/
class Order
/**
* #var integer
*
* #ORM\Column(name="personid", type="integer", nullable=false)
*/
private $personid;
/**
* #ORM\OneToOne(targetEntity="People")
* #ORM\JoinColumn(name="personid", referencedColumnName="personid")
*/
private $person;
public function getPersonId()
{
return $this->personid;
}
public function getPerson()
{
return $this->person;
}
}
I realize that if I call $order->getPersonId() it return always an empty value and I have to call the getPerson()->getId() method to get the correct personid.
Could anyone explain me why the variable $personid is not filled?
Should I to delete the column id used for the join if I defined one?
Thanks
Gisella
You should remove private $personid;, it's better to work with objects only in an ORM.
It's not a problem if you get the ID with $order->getPerson()->getId(), because Doctrine won't load the complete entity. The People entity will only be loaded if you call an other field than the join key.
You can still have a getter shortcut like this :
public function getPersonId()
{
return $this->getPerson()->getId();
}
Edit :
You can also still work with "ID" if you use Doctrine references, like this :
$order->setPerson($em->getReference('YourBundle:People', $personId));
With this way, Doctrine won't perform a SELECT query to load data of the person.
You don't need to have the $personid field when you already have the $person field.
$people contains the People object (with all People's attributes including the id).
Moreover, when doctrine translate your object into sql tables, he knows that he have to join with th id so it will create a field (in database) named personid. (It's the name that you defined in your ORM)
/**
* #ORM\OneToOne(targetEntity="People")
* #ORM\JoinColumn(name="personid", referencedColumnName="personid")
*/
private $person;
Sorry for bad english :p

Symfony2/Doctrine joined query

I need to create a simple query that produces a result set of a database entry plus the username of the person that posted it.
I've tried to setup the associations properly but I'm not sure if that's right either. I'm finding the whole idea of using these small string identifiers quite confusing. Surely there must be a simpler way of doing a join?
My two entities:
class Users
{
// ...
/**
* #ORM\Column(type="string")
* #ORM\OneToMany(targetEntity="Titles", mappedBy="addedBy")
*/
protected $username;
// ..
}
and
class Titles
{
// ....
/**
* #ORM\Column(type="string")
* #ORM\ManyToOne(targetEntity="Users", inversedBy="username")
*/
protected $addedBy;
// ....
}
with the following in the controller:
$titles = $em->createQueryBuilder()
->select('t.*', 'u.*')
->from('dvdLoggerdvdBundle:Titles', 't')
->leftJoin('t.addedBy', 'u')
->addOrderBy('t.title', 'DESC')
->getQuery()
->getResult();
I'm getting the following error:
[Semantical Error] line 0, col 69 near 'u ORDER BY t.title': Error: Class
dvdLogger\dvdBundle\Entity\Titles has no association named addedBy `
Update 1
I made all the changes suggested by Tom and did lots of reading!
It appears that in order to overcome the lazy loading feature I need to carry out a leftJoin. I have rewritten my query as follows:
public function getAllTitles()
{
// view all records in db
$titles = $this->createQueryBuilder('t')
->select('t, u')
->leftJoin('t.addedBy', 'u')
->addOrderBy('t.title', 'DESC');
return $titles->getQuery()->getResult();
}
I am getting a result set, but the addedBy is returning NULL when I dump the result set. As far as I'm aware shouldn't this pull the associated field in from the other table?
Best practice is to reference the entity by its id, you are trying to reference it using the username. The inversed field should also be a specific field not an existing one that holds data. And keep it mind this field is optional and defines the associations as bidirectional, for the specified use case you don't actually need it as you are joining from the Titles entity. I would advice reading the doc here http://symfony.com/doc/current/book/doctrine.html#entity-relationships-associations as well as here http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html
Bidirectional association (w/ inversed field)
First get rid of that line:
#ORM\Column(type="string")
In your $addedBy annotations and change inverseBy="username" to inversedBy="titles" (note the typo)
You optionaly could add
#ORM\JoinColumn(name="user_id", referencedColumnName="id")
Then in your Users Entity add
/**
*
* #ORM\OneToMany(targetEntity="Titles", mappedBy="addedBy")
*/
protected $titles;
And get rid of
* #ORM\OneToMany(targetEntity="Titles", mappedBy="addedBy")
In your $username annotations
Last make sure you update the database schema
Then your query should return the expected result.
Unidirectional association (w/out inversed field)
Get rid of
* #ORM\OneToMany(targetEntity="Titles", mappedBy="addedBy")
In your $username annotations
Then get rid of that line in your $addedBy annotations:
#ORM\Column(type="string")
As well as inverseBy="username"
You optionaly could add
#ORM\JoinColumn(name="user_id", referencedColumnName="id")
Last make sure you update the database schema
Then your query should return the expected result.

Default value in Doctrine

How do I set a default value in Doctrine 2?
<?php
/**
* #Entity
*/
class myEntity {
/**
* #var string
*
* #ORM\Column(name="myColumn", type="integer", options={"default" : 0})
*/
private $myColumn;
...
}
Note that this uses SQL DEFAULT, which is not supported for some fields like BLOB and TEXT.
Database default values are not "portably" supported. The only way to use database default values is through the columnDefinition mapping attribute where you specify the SQL snippet (DEFAULT cause inclusive) for the column the field is mapped to.
You can use:
<?php
/**
* #Entity
*/
class myEntity {
/**
* #var string
*
* #Column(name="myColumn", type="string", length="50")
*/
private $myColumn = 'myDefaultValue';
...
}
PHP-level default values are preferred as these are also properly available on newly created and persisted objects (Doctrine will not go back to the database after persisting a new object to get the default values).
Set up a constructor in your entity and set the default value there.
Use:
options={"default":"foo bar"}
and not:
options={"default"="foo bar"}
For instance:
/**
* #ORM\Column(name="foo", type="smallint", options={"default":0})
*/
private $foo
Update
One more reason why read the documentation for Symfony will never go out of trend. There is a simple solution for my specific case and is to set the field type option empty_data to a default value.
Again, this solution is only for the scenario where an empty input in a form sets the DB field to null.
Background
None of the previous answers helped me with my specific scenario but I found a solution.
I had a form field that needed to behave as follow:
Not required, could be left blank. (Used 'required' => false)
If left blank, it should default to a given value. For better user experience, I did not set the default value on the input field but rather used the html attribute 'placeholder' since it is less obtrusive.
I then tried all the recommendations given in here. Let me list them:
Set a default value when for the entity property:
<?php
/**
* #Entity
*/
class myEntity {
/**
* #var string
*
* #Column(name="myColumn", type="string", length="50")
*/
private $myColumn = 'myDefaultValue';
...
}
Use the options annotation:
#ORM\Column(name="foo", options={"default":"foo bar"})
Set the default value on the constructor:
/**
* #Entity
*/
class myEntity {
...
public function __construct()
{
$this->myColumn = 'myDefaultValue';
}
...
}
None of it worked and all because of how Symfony uses your Entity class.
IMPORTANT
Symfony form fields override default values set on the Entity class.
Meaning, your schema for your DB can have a default value defined but if you leave a non-required field empty when submitting your form, the form->handleRequest() inside your form->isValid() method will override those default values on your Entity class and set them to the input field values. If the input field values are blank, then it will set the Entity property to null.
http://symfony.com/doc/current/book/forms.html#handling-form-submissions
My Workaround
Set the default value on your controller after form->handleRequest() inside your form->isValid() method:
...
if ($myEntity->getMyColumn() === null) {
$myEntity->setMyColumn('myDefaultValue');
}
...
Not a beautiful solution but it works. I could probably make a validation group but there may be people that see this issue as a data transformation rather than data validation, I leave it to you to decide.
Override Setter (Does Not Work)
I also tried to override the Entity setter this way:
...
/**
* Set myColumn
*
* #param string $myColumn
*
* #return myEntity
*/
public function setMyColumn($myColumn)
{
$this->myColumn = ($myColumn === null || $myColumn === '') ? 'myDefaultValue' : $myColumn;
return $this;
}
...
This, even though it looks cleaner, it doesn't work. The reason being that the evil form->handleRequest() method does not use the Model's setter methods to update the data (dig into form->setData() for more details).
Here is how to do it in PHP 8 using attributes.
#[ORM\Column(type: 'boolean', nullable: false, options: ['default' => 0])]
#[Assert\NotNull()]
private bool $isFavorite = false;
The workaround I used was a LifeCycleCallback. Still waiting to see if there is any more "native" method, for instance #Column(type="string", default="hello default value").
/**
* #Entity #Table(name="posts") #HasLifeCycleCallbacks
*/
class Post implements Node, \Zend_Acl_Resource_Interface {
...
/**
* #PrePersist
*/
function onPrePersist() {
// set default date
$this->dtPosted = date('Y-m-d H:m:s');
}
You can do it using xml as well:
<field name="acmeOne" type="string" column="acmeOne" length="36">
<options>
<option name="comment">Your SQL field comment goes here.</option>
<option name="default">Default Value</option>
</options>
</field>
Here is how I solved it for myself. Below is an Entity example with default value for MySQL. However, this also requires the setup of a constructor in your entity, and for you to set the default value there.
Entity\Example:
type: entity
table: example
fields:
id:
type: integer
id: true
generator:
strategy: AUTO
label:
type: string
columnDefinition: varchar(255) NOT NULL DEFAULT 'default_value' COMMENT 'This is column comment'
None of this worked for me. I found some documentation on doctrine's site that says to set the value directly to set a default value.
https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/faq.html#how-can-i-add-default-values-to-a-column
private $default = 0;
This inserted the value I wanted.
Works for me on a mysql database also:
Entity\Entity_name:
type: entity
table: table_name
fields:
field_name:
type: integer
nullable: true
options:
default: 1
Adding to #romanb brilliant answer.
This adds a little overhead in migration, because you obviously cannot create a field with not null constraint and with no default value.
// this up() migration is autogenerated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != "postgresql");
//lets add property without not null contraint
$this->addSql("ALTER TABLE tablename ADD property BOOLEAN");
//get the default value for property
$object = new Object();
$defaultValue = $menuItem->getProperty() ? "true":"false";
$this->addSql("UPDATE tablename SET property = {$defaultValue}");
//not you can add constraint
$this->addSql("ALTER TABLE tablename ALTER property SET NOT NULL");
With this answer, I encourage you to think why do you need the default value in the database in the first place? And usually it is to allow creating objects with not null constraint.
If you use yaml definition for your entity,
the following works for me on a postgresql database:
Entity\Entity_name:
type: entity
table: table_name
fields:
field_name:
type: boolean
nullable: false
options:
default: false
While setting the value in the constructor would work, using the Doctrine Lifecycle events might be a better solution.
By leveraging the prePersist Lifecycle Event, you could set your default value on your entity only on initial persist.
I struggled with the same problem. I wanted to have the default value from the database into the entities (automatically). Guess what, I did it :)
<?php
/**
* Created by JetBrains PhpStorm.
* User: Steffen
* Date: 27-6-13
* Time: 15:36
* To change this template use File | Settings | File Templates.
*/
require_once 'bootstrap.php';
$em->getConfiguration()->setMetadataDriverImpl(
new \Doctrine\ORM\Mapping\Driver\DatabaseDriver(
$em->getConnection()->getSchemaManager()
)
);
$driver = new \Doctrine\ORM\Mapping\Driver\DatabaseDriver($em->getConnection()->getSchemaManager());
$driver->setNamespace('Models\\');
$em->getConfiguration()->setMetadataDriverImpl($driver);
$cmf = new \Doctrine\ORM\Tools\DisconnectedClassMetadataFactory();
$cmf->setEntityManager($em);
$metadata = $cmf->getAllMetadata();
// Little hack to have default values for your entities...
foreach ($metadata as $k => $t)
{
foreach ($t->getFieldNames() as $fieldName)
{
$correctFieldName = \Doctrine\Common\Util\Inflector::tableize($fieldName);
$columns = $tan = $em->getConnection()->getSchemaManager()->listTableColumns($t->getTableName());
foreach ($columns as $column)
{
if ($column->getName() == $correctFieldName)
{
// We skip DateTime, because this needs to be a DateTime object.
if ($column->getType() != 'DateTime')
{
$metadata[$k]->fieldMappings[$fieldName]['default'] = $column->getDefault();
}
break;
}
}
}
}
// GENERATE PHP ENTITIES!
$entityGenerator = new \Doctrine\ORM\Tools\EntityGenerator();
$entityGenerator->setGenerateAnnotations(true);
$entityGenerator->setGenerateStubMethods(true);
$entityGenerator->setRegenerateEntityIfExists(true);
$entityGenerator->setUpdateEntityIfExists(false);
$entityGenerator->generate($metadata, __DIR__);
echo "Entities created";
Be careful when setting default values on property definition! Do it in constructor instead, to keep it problem-free. If you define it on property definition, then persist the object to the database, then make a partial load, then not loaded properties will again have the default value. That is dangerous if you want to persist the object again.

Categories