Unknown Column Type "Varchar" - php

Using Doctrine, I am being presented the following error:
[2016-09-14 21:24:44] request.CRITICAL: Uncaught PHP Exception Doctrine\DBAL\DBALException: "Unknown column type "varchar" 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::getTypeMap(). If this error occurs during database introspection then you might have forgot to register all database types for a Doctrine Type. Use AbstractPlatform#registerDoctrineTypeMapping() or have your custom types implement Type#getMappedDatabaseTypes(). If the type name is empty you might have a problem with the cache or forgot some mapping information." at /var/www/project/apps/ProjectName/trunk/vendor/doctrine/dbal/lib/Doctrine/DBAL/DBALException.php line 114 {"exception":"[object] (Doctrine\\DBAL\\DBALException(code: 0): Unknown column type \"varchar\" 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::getTypeMap(). If this error occurs during database introspection then you might have forgot to register all database types for a Doctrine Type. Use AbstractPlatform#registerDoctrineTypeMapping() or have your custom types implement Type#getMappedDatabaseTypes(). If the type name is empty you might have a problem with the cache or forgot some mapping information. at /var/www/project/apps/ProjectName/trunk/vendor/doctrine/dbal/lib/Doctrine/DBAL/DBALException.php:114)"} []
the relevant class looks as such
<?php
namespace Project\DBALBundle\Entity\Url;
use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation as JMSS;
/**
* Profile
*
* #ORM\Table(name="Profile")
* #ORM\Entity(repositoryClass="Project\DBALBundle\Entity\Url\ProfileRepository")
*/
class ProfileRepository {
/**
* #var string $id
*
* #ORM\Column(name="id", type="integer", length=11, nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string $label
*
* #ORM\Column(name="label", type="string", length=50, nullable=false)
*/
private $label;
/**
* #return string
*/
public function getId()
{
return $this->id;
}
/**
* #param string $id
*/
public function setId($id)
{
$this->id = $id;
}
/**
* #return string
*/
public function getLabel()
{
return $this->label;
}
/**
* #param string $label
*/
public function setLabel($label)
{
$this->label = $label;
}
public function __toString()
{
return $this->label;
}
}
With the above class and annotation-defined mappings, I receive the error. However, if I change the field private $label to private $labelField and update the associated references, everything works just fine and the data is accessed as expected.
Insofar as I have been able to search, there is nothing special about the field private $label. It is not a reserved keyword, and I can find nothing mentioning anything special about it either with PHP itself or Doctrine specifically. So why does this break?

My guess will be it's a caching problem. You had probably tried at some point this code,
/**
* #var string $label
*
* #ORM\Column(name="label", type="varchar", length=50, nullable=false)
*/
private $label;
and this class has got cached by opcache (or similar). Opcache pays no attention to annotations (it's just a comments for it), so no matter what you change in annotations, it's still using this cached version of the code.
But when you change a property name, it realizes that it's a newer version of class and parses annotations once again (that's why with labelField code worked).
But that's just a speculation. I would try to debug it with Xdebug to find out an exact problem.
P.S. Doctrine version 2.3 is quite old, isn't it?

Related

Symfony Doctrine does not hydrate the whole chain

I simplified my 3 entities as much as possible below, it shows a simple relationship of Currency <- 1:1 -> Balance <- 1:N -> BalanceLog
Entity/Currency.php
/**
* #ORM\Entity(repositoryClass=CurrencyRepository::class)
*/
class Currency
{
/**
* #ORM\Id
* #ORM\Column(type="string", length=3)
*/
private ?string $code;
/**
* #ORM\OneToOne(targetEntity="Balance", mappedBy="currency")
**/
private ?Balance $balance;
// ...
}
Entity/Balance.php
/**
* #ORM\Entity(repositoryClass=BalanceRepository::class)
*/
class Balance
{
/**
* #ORM\Id
* #ORM\OneToOne(targetEntity="Currency", inversedBy="balance")
* #ORM\JoinColumn(name="currency", referencedColumnName="code", nullable=false)
**/
private ?Currency $currency;
/**
* #ORM\OneToMany(targetEntity="App\Entity\BalanceLog", mappedBy="balance")
*/
private Collection $balance_logs;
// ...
}
Entity/BalanceLog.php
/**
* #ORM\Entity(repositoryClass=BalanceLogRepository::class)
*/
class BalanceLog
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private ?int $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Balance", inversedBy="balance_logs")
* #ORM\JoinColumn(name="balance_currency", referencedColumnName="currency")
**/
private ?Balance $balance;
// ...
}
The issue happens when I call:
$balanceLog = $this->getDoctrine()
->getRepository('App:BalanceLog')->findAll();
This hydrates the BalanceLog::$balance to the proper instance of Balance type, but it does not hydrate the BalanceLog::$balance->currency to Currency instance. Instead it wants to use string only
Resulting in error:
Typed property App\Entity\Balance::$currency must be an instance of App\Entity\Currency or null, string used
The dirty fix is to make Balance::$currency without fixed type of ?Currency. Then it will accept string and the code "works". But it is not correct. The Balance::$currency should be of Currency type, not sometimes string, sometimes currency.
I tried to make my own method in BalanceLogRepository, and for whatever reason this works just fine:
public function findByBalance(Balance $balance) : iterable
{
$query = $this->createQueryBuilder('bl');
$query->andWhere('bl.balance = :balance')
->setParameter('balance', $balance);
return $query->getQuery()->getResult();
}
So I am even more perplexed as to why the default findAll or findBy does not do recursive hydration
After further investigation I found a very weird behavior:
if I prepend this code:
$balance = $this->getDoctrine()->getRepository('App:Balance')->find('USD');
in front of
$balanceLog = $this->getDoctrine()->getRepository('App:BalanceLog')->findAll();
in my controller, then the error is gone. Its as if the App:Balance ORM schema of Balance with dependencies were not properly loaded until I try to fetch the Balance object directly apriori.
I did some debugging and it looks that BalanceLog does not create a full Balance Entity instance, but instead a Proxy. The solution was to add eager loading to the BalanceLog class
class BalanceLog
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private ?int $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Balance", inversedBy="balance_logs", fetch="EAGER")
* #ORM\JoinColumn(name="balance_currency", referencedColumnName="currency")
**/
private ?Balance $balance;
// ...
}
The UnitOfWork.php then does not use Proxy but instead loads the Entity as a whole.
If somebody wonders why querying Balance beforehand made the code work, its because of sophisticated caching mechanism of Doctrine. It saved Balance instance for primary key USD and then when BalanceLog was populated, it used this instance instead of creating a Proxy.
I still think that Proxy should not enforce strictly typed property from Entity though, but this is something for Doctrine developers to decide.

Add extra option to LoggableListener

I use Loggable to backup changes in Entities.
The default AbstractLogEntry does not have enough columns for my needs.
Thats why i extended the class and added extra getters and setters.
See the code below
/**
* EmployeeBackup
*
* #ORM\Table(name="employee_backup")
* #ORM\Entity(repositoryClass="Gedmo\Loggable\Entity\Repository\LogEntryRepository")
*
*/
class EmployeeBackup extends AbstractLogEntry
{
/**
* #var int
*
* #ORM\Column(name="division_id", type="integer", unique=true)
*/
private $divisionId;
/**
* #return int
*/
public function getDivisionId(): int
{
return $this->divisionId;
}
/**
* #param string $divisionId
*/
public function setDivisionId(string $divisionId): void
{
$this->divisionId = $divisionId;
}
}
The extension is using the class above. So it works.
But now i need to set the divisionId when a new version is stored.
I tried the code below
$loggable = new LoggableListener();
$loggable->setDivision($division);
$evm->addEventSubscriber($loggable);
And this is what i get:
Attempted to call an undefined method named "setDivision" of class "Gedmo\Loggable\LoggableListener".
And thats true because LoggableListener does not have a setDivision function. My question is: Do i need to override the listener and if so, how do i do that?
Thanks ;)

EasyAdmin: "Unable to transform value for property path XY: Expected a string."

When clicking the "Edit" link in EasyAdmin's list view of an entity that contains a field with type="date", I'm getting this error message:
Unable to transform value for property path "birthday": Expected a string.
I have this in my entity:
/**
* #ORM\Column(type="date")
* #Assert\NotBlank()
* #Assert\Date()
*/
private $birthday;
There are 2 solutions.
Quick and dirty (Symfony < 5)
Change this in config/packages/easy_admin.yaml:
easy_admin:
entities:
MyEntity:
form:
fields:
- { property: 'birthday', type: 'date' }
See https://symfony.com/doc/master/bundles/EasyAdminBundle/book/edit-new-configuration.html#the-special-form-view for further configuration details.
Quick and clean
#Assert\Date() will be deprecated for type="date" fields in Symfony 4.2 (and thus probably removed in Symfony 5). The validation relies on the \DateTimeInterface type hint of the setter. In total:
/**
* #ORM\Column(type="date")
* #Assert\NotBlank()
*/
private $birthday;
public function setBirthday(?\DateTimeInterface $birthday): self
{
// ...
return $this;
}
See https://github.com/EasyCorp/EasyAdminBundle/issues/2381 for some background information.

Doctrine2 ORM - A managed+dirty entity X can not be scheduled for insertion

I've got the following classes (only show partials to reduce amount to read)
class Page {
/**
* #ORM\Column(type="string", unique=true, nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="UUID")
* #var string
*/
protected $id;
/**
* #ORM\OneToMany(targetEntity="Url", mappedBy="content")
* #var Url[]
*/
protected $urls;
public function __construct()
{
$this->urls = new ArrayCollection();
}
}
And:
class Url
{
/**
* #ORM\Id #ORM\Column(type="string", unique=true, nullable=false)
* #ORM\GeneratedValue(strategy="UUID")
* #var string The unique identifier for the Url
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Page", inversedBy="urls", cascade={"persist", "merge"})
* #ORM\JoinColumn(name="content_id", referencedColumnName="id")
* #var int The UUID of the content
*/
protected $content;
public function __construct(Page $content, $link)
{
$this->content = $content;
$this->content->addUrl($this);
}
}
Each of these has a manager class with a save() function which just uses persist() and flush(). Saving them is then done as:
$pageManager->save($post);
$url = new Url($post, 'link goes here');
$urlManager->save($url);
I've also tried:
$url = new Url($post, 'link goes here');
$pageManager->save($post);
$urlManager->save($url);
Though in both instances I get:
( ! ) Fatal error: Uncaught exception 'Doctrine\ORM\ORMInvalidArgumentException' with message 'A managed+dirty entity Page#000000003d5a4ca10000000133ba3c3e can not be scheduled for insertion.'
I've tried this both with and without using AnnotationReader being used with EntityManager::create()
Doctrine's schema validator doesn't report any errors either:
php vendor/bin/doctrine orm:validate-schema
[Mapping] OK - The mapping files are correct.
[Database] OK - The database schema is in sync with the mapping files.
Any ideas how to get the persist() to work?
figured it out:
I had to reverse the order they were saved in, so the Url is saved first, and then Page (though in doing so it's opened up another issue I need to resolve where it wants to persist the author entity but can't (it thinks it's new, but it's not). Thought maybe it was detached, but even a merge() doesn't solve it.
/**
* #ORM\ManyToOne(targetEntity="User")
* #ORM\JoinColumn(name="author_id", referencedColumnName="id")
* #var string The UUID of the author for the {#link Page}
*/
protected $author;
You must persist the $post and the $url objects before flush(). Otherwise you're going to have this error message.
Try to do this...
$entityManager->persist($post);
$url = new Url($post, 'link goes here');
$entityManager->persist($url);
$entityManager->flush();
Or you can create a flag param on your managers, to not flush if false...
$flush = false;
$pageManager->save($post, $flush);
$url = new Url($post, 'link goes here');
$urlManager->save($url);
It will probably work without any additional resources.

Doctrine and Symfony2 : Association refers to inverse side that doesn't exist error

I have a page address.html.twig , the user can add many addresses in the table UserAddress. when he added his address in the database , the address should be render in the same page that he added his address then he can choose which one he would like to use. Unfortunately the address is not render.
First i thought that i have a problem in my controller action or in my twig page. I even asked a question here about it => here
I verified all my tables in phpmyadmin and all of them are well link but if i'm doing this: php app/console doctrine:schema:validate
i have this error :
[Mapping] FAIL - The entity-class
'FLY\BookingsBundle\Entity\Commandes' mapping is invalid:
* The association FLY\BookingsBundle\Entity\Commandes#user refers to the inverse side field
Application\Sonata\UserBundle\Entity\User#commandes which does not
exist.
[Mapping] FAIL - The entity-class
'FLY\BookingsBundle\Entity\UserAddress' mapping is invalid:
* The association FLY\BookingsBundle\Entity\UserAddress#user refers to the inverse side field
Application\Sonata\UserBundle\Entity\User#address which does not
exist.
Have a look at this picture:
This is my UserAddress.php
/**
* #ORM\ManyToOne(targetEntity="Application\Sonata\UserBundle\Entity\User", inversedBy="address")
* #ORM\JoinColumn(nullable=true)
*/
private $user;
Commandes.php
/**
* #ORM\ManyToOne(targetEntity="Application\Sonata\UserBundle\Entity\User", inversedBy="commandes")
* #ORM\JoinColumn(nullable=true)
*/
private $user;
User.php
/**
* #ORM\Entity(repositoryClass="FLY\UserBundle\Repository\UserRepository")
* #ORM\Table(name="fos_user_user")
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
public function __construct()
{
parent::__construct();
$this->commandes = new \Doctrine\Common\Collections\ArrayCollection();
$this->address = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* #ORM\OneToMany(targetEntity="FLY\BookingsBundle\Entity\Commandes", mappedBy="user", cascade={"remove"})
* #ORM\JoinColumn(nullable=true)
*/
private $commandes;
/**
* #ORM\OneToMany(targetEntity="FLY\BookingsBundle\Entity\UserAddress", mappedBy="user", cascade={"remove"})
* #ORM\JoinColumn(nullable=true)
*/
private $address;
Here you can see my var dump:
User {#124 ▼
#id: 21
-commandes: null
-address: null
}
I've had an issue which has popped up 2-3 times in the last few years, where the mappings were incorrect but the schema update was successful. After the mappings were fixed this wasn't reflected in the schema and symfony assumed it was already up-to-date.
I recommend you try removing the relevent relationships manually from your user, commande and address tables and then run:
php app/console doctrine:schema:update --force
- it may fix your issue.
Heres an example from one of my apps - I've done this for your commandes entity.
You'll be able to piece together your UserAddress Entity from this example yourself!
Here goes:
User.php
/**
* #ORM\OneToMany(targetEntity="FLY\BookingsBundle\Entity\Commandes", mappedBy="commandesUser")
*/
protected $commandes;
User.php - Getters and Setters
/**
* Add commandes
*
* #param FLY\BookingsBundle\Entity\Commandes $commandes
*/
public function addCommandes(\FLY\BookingsBundle\Entity\Commandes $commandes)
{
$this->commandes[] = $commandes;
}
/**
* Get commandes
*
* #return Doctrine\Common\Collections\Collection
*/
public function getCommandes()
{
return $this->commandes;
}
Commandes.php
/**
* #ORM\ManyToOne(targetEntity="Application\Sonata\UserBundle\Entity\User", inversedBy="commandes")
* #ORM\JoinColumn(name="user", referencedColumnName="id")
*/
private $commandesUser;
Commandes.php - Getters and Setters
/**
* Set commandesUser
*
* #param Application\Sonata\UserBundle\Entity\User $commandesUser
*/
public function setCommandesUser(\Application\Sonata\UserBundle\Entity\User $commandesUser = null)
{
$this->commandesUser = $commandesUser;
}
/**
* Get $commandesUser
*
* #return Application\Sonata\UserBundle\Entity\User
*/
public function getCommandesUser()
{
return $this->commandesUser;
}
It's quite likely this doesn't happen to anyone else, but there's a chance.
In my case, this error appeared because there was a duplicate. My entity had 2 fields, which are ManyToOne relationships. And they both had the same inversed names, which gave this error.
So this is the relevant bit of code:
class TaskIngredient
{
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Ingredient", inversedBy="taskIngredients")
* #ORM\JoinColumn(nullable=false)
*/
private $ingredient;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Task", inversedBy="taskIngredients")
* #ORM\JoinColumn(nullable=false)
*/
private $task;
}
The solution was relatively easy. I tried changing the inversedBy name, manually. However this didn't fix it (even after applying php app/console doctrine:schema:update --force and removing the var/cache folder).
So I just:
Removed one of the problematic entities (and it's setter/getter)
Ran the php bin/console make:entity tool and readded the field with a different name
Voilà! Issue fixed.

Categories