Doctrine2 bug imports 'private' variables from MappedSuperClass when generating entities - symfony

I'm using Symfony's entity classes in conjunction with Doctrine's ORM annotation to persist the values to the database. Most tables need a few standard fields, so I have created a base entity that all other entities can extend. According to the documentation this is called a MappedSuperClass: http://doctrine-orm.readthedocs.org/en/latest/reference/inheritance-mapping.html
// src/Acme/Bundle/Entity/Base.php
namespace Acme\Bundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\MappedSuperclass
* #ORM\HasLifecycleCallbacks()
*/
class Base {
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
// more values...
}
I then create multiple entities that extend this base:
// src/Acme/Bundle/Entity/View.php
namespace Acme\Bundle\Entity;
use Doctrine\ORM\Mapping as ORM;
View extends Entity\Base
{
// entity definitions
}
Is this the best way to set default definitions for an entity? If you have better suggestions, let me know.
Next, when I generate entities via:
php app/console doctrine:generate:entities Acme\Bundle
It works great the first time to create the getters and setters, but if I make changes to an entity and generate again, it gives an error like this:
Fatal error: Access level to Acme\Bundle\Entity\View::$id must be protected (as in class Acme\Bundle\Entity\Base) or weaker in /src/Acme/Bundle/Entity/View.php
This is happening because doctrine:generate:entities is importing the 'protected' variables from the MappedSuperClass into the extending entity as 'private'.
Others have complained about this error in other contexts without a solution:
FOSUserBundle generate:entities does not work, Access level of fields too high
https://github.com/FriendsOfSymfony/FOSUserBundle/issues/102
QUESTION: Is doctrine supposed to be importing the protected variables? And is it supposed to be setting them as "private"? Or is this just a known Symfony bug?
It seems like it should not import the protected variables since the #ORM definitions are in the MappedSuperClass already and those are not imported (and when I delete the imported private variables it works fine). But if it does import them, it should not be setting them as private...
I literally have to do a search and replace through all my entities to delete these. Every. Single. Time.
What is the suggested course of action here? If this is a bug, has someone reported this and what is the timeline for fixing? How should I search for this issue on github and report it if it's not reported?
Question 2: As long as this bug exists, is there a way to just generate the getters/setters on a Single Entity?
e.g. php app/console doctrine:generate:entities Acme\Bundle\Entity\View (this doesn't work of course). If I could generate only one entity at a time, it would be less of a hassle deleting all the imported private variables across all my entities.
[EDIT: I have answered this question below]

The answer to #2 is, yes, you can generate entity getters/setters for just one single entity.
From the command line, if you type:
php app/console doctrine:generate:entities --help
You get a list of options. There you will see how to limit entities to bundle, single entity in a bundle, or an entire namespace:
You have to limit generation of entities:
* To a bundle:
php app/console doctrine:generate:entities MyCustomBundle
* To a single entity:
php app/console doctrine:generate:entities MyCustomBundle:User
php app/console doctrine:generate:entities MyCustomBundle/Entity/User
* To a namespace
php app/console doctrine:generate:entities MyCustomBundle/Entity

For the question 1, you need to redefine each primary key when entities are inherited.

Related

How to rename Entity and attribute in symfony

I just want to rename "EntityName" to something else the most easily possible, with the database entry. Also how can I rename an Attribute?
So, I have an Entity called "EntityName" (for exemple) in /appname/src/Entity/EntityName.php like this:
<?php
namespace App\Entity;
use App\Repository\EntityNameRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass=EntityNameRepository::class)
*/
class EntityName
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
[...]
When I try to rename the name of the class and try php bin/console doctrine:schema:update --dump-sql or --force I get this error:
In EntityName.php line 11:
Compile Error: Cannot declare class App\Entity\NewName, because the name is already in use
(I think I cannot rename beaucause it check itself if it already exist.)
I'd advise caution, specially if it's a production environment.
doctrine:schema:update --force (or doctrine:migrations:diff or make:migration, for that matter) doesn't detect changes so it will drop your existing table (along with all your data) and create a new one.
The safest way to preserve data would be to hand-create a migration with the ALTER TABLE ... RENAME. You can also use the #Table(name="old_table_name") annotation to preserve the original name, although this might be a bit confusing when looking at the database alone.
Depending on your IDE you might need more or less elbow grease. Chances are that the following will be handled correctly:
Both the entity and repository class name and file name, making them match (even in the caseName).
The entity class name in the repository constructor.
The repository class name in the repositoryClass annotation of the entity.
targetEntity annotations in relations.
There will be the property names themselves in the related classes (and JoinTableand mappedBy / inversedBy if applicable) left to change, if you wish to keep things consistent. If so, you would have to add the needed statements to the migration as well, and you might need to disable the constraints in the process. --dump-sql might be helpful on hinting what tables need renaming, but you won't be able to use the sql straight up.
The easiest way, if you don't care about your data, would be doctrine:schema:update --force indeed.
If you want to rename the entity means you have to change both Entity and Repository Class name and file name too. Then have to change Both classes import statements properly. After that just run this comment bin/console c:c.
If you want to rename an entity, you only need to change its class name AND file name to make it match. Also make sure the namespace match file path.
If all these things are right, then make sure you don't have another file which would contain the same namespace and class name you are trying to use for your "NewName" class
If you still get the error message, then you might want to clear you cache by running php bin/console c:c
Lastly, if it still fails, you might want to dump the autoload by running composer dump-autoload (or if you are using Phpstorm: tools > composer > dump autoloader)

Using htmlpurifier with annotations in symfony 2.8

I would like to use the htmlpurifier not as a form data transformer, but rather as an annotation in the entities property.
The problem: In the Symfony 2.8 project we use a lot of different types of forms, some using symfonies formbuilder, some via sonata-admin, some using twig and ajax etc ..
So using a transformer and a custom field type is not the way to go.
Also, editing every single create or update action would be overkill.
I've been playing with the idea of an event listener on prePersist, but with such a large amount of different entities in our project we would most likely incur a performance hit if this were to be called on every persist() action. Limiting it to only specific object types would also be painstaking.
Is it possible to use the htmlpurifier bundle as an annotation only on the properties that matter?
eg:
/**
* #var string
*
* #ORM\Column(name="uprename", length=20, nullable=true)
* #htmlpurifier('default')
*/
private $firstName;
Where default would be the default configuration.
bundle: https://github.com/Exercise/HTMLPurifierBundle

Symfony Login form with existing user class

I'm trying to use (and understand) how Security works in Symfony. I've created a login form and it works with hard-coded users.
Now I want to use an existing user table in my database. The table has all the requiered fields but with different column names. The entity also exists, also with different names (for example "customUserId" instead of "id").
Something like (with "MAGIC_MAPPING"):
/**
* #ORM\Table(name="custom_user_table")
* #ORM\Entity
*/
class User implements UserInterface, \Serializable
{
/**
* #ORM\Column(name="customUserId", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
* #MAGIC_MAPPING so it links this to the mandatory "id" field
*/
private $customUserId;
...
}
Is there a way to map my existing fields so Symfony uses them for login purpose? Or at least can I make it work without changing my database structure (only the entity class)?
I've tried seleveral actions, from this forum and from Symfony documentation, but it always ends with ugly error I can't fix.
Any idea or lead? Thanks a lot for your help, I've been struggling on this for several hours now...
You have to change the providers configuration inside the security.yml file
here is an example, i use the email field from my administrator entity, I think you can do the same with your entity
providers:
administrator:
entity: { class: Entity:Administrator, property: email }
Try changing the ORM file of that table.
create an ORM file using Command promt.
then edit the orm file .
<mapped-superclass name="FOS\UserBundle\Entity\User" table="your_table_name">
/.../
</mapped>
BenoƮt 's answer help me figure it out. I gave too much attention to Symfony documentation's creation process that I forgot the essential: the UserInterface. No need to map anything, just implement this interface and defined methods like:
public function getUsername() {
return $this->customLogin;
}
Then update the provider configuration (thanks Benoit):
providers:
db_sam_provider:
entity:
class: AppBundle:User
property: customLogin
The other parts are conform to Symfony documentation: here and here

Add a custom function in a vendor repository

I'm using CCDNForum's ForumBundle (the bundle itself is not relevant, my question isn't related to this bundle specifically but is more general) in my Symfony website, and I want to customize it, so naturally I created a AcmeForumBundle whose parent is CCDNForumForumBundle. The only thing I want to change is to add a new custom function in let say the CategoryRepository associated to the Category entity.
So I created a CategoryRepository.php in my AcmeForumBundle extending the CategoryRepository.php of CCDNForum, and this is not sufficient because the default repository of the Category entity is CCDNForum's CategoryRepository.
The next thing I did was to create a new AcmeForumBundle Category entity extending CCDNForum's Category entity, changing of course the default associated repository
namespace Acme\ForumBundle\Entity;
use CCDNForum\ForumBundle\Entity\Category as BaseCategory;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection as ArrayCollection;
/**
* #ORM\Entity
* #ORM\Entity(repositoryClass="Acme\ForumBundle\Repository\CategoryRepository")
* #ORM\Table(name="CC_Forum_Category")
*/
class Category extends BaseCategory
{
}
I added the #ORM\Table line because I had an SQL error "acme_category" table not found. The class is of course empty as I have nothing to change in the entity.
Using this code everything works fine, I added my custom function in the CategoryRepository without problem, but the problem comes when I want to update the database schema. I have the error when running php app/console doctrine:schema:update --dump-sql
[Doctrine\DBAL\Schema\SchemaException]
The table with name 'acme.cc_forum_category' already exists.
which I can understand since CCDNForum's Category entity and my Acme Category entity are using the same table (the C_Forum_Category table).
My question is: am I doing all this right ? Isn't there a simpler way to add a custom function in a vendor repository ?
Thanks!

How can I use Doctrine Annotations to change a column name in an entity subclass?

I'm using FOSUserBundle with a new Symfony project that has to work with an existing schema. My user entity extends FOS\UserBundle\Entity\User as the instructions say, but the email column is named "email_addr" instead of "email". Since the parent defines $email I can't re-declare it to attach my annotation.
/**
* #var string $emailAddr
*
* #ORM\Column(name="email_addr", type="text", nullable=false)
*/
protected $email;
The exception I get is:
[Doctrine\ORM\Mapping\MappingException]
Property "email" in "Foo\DataBundle\Entity\User" was already declared, but it must be declared only once
My question is either:
How can a Doctrine2 subclass use an annotation to alter something defined in the parent?
Or how can I override column names in the FOSUserBundle?
I found an answer:
More about Doctrine implementations
If you need to change the mapping (for instance to adapt the field names to a legacy database), the only solution is to write the whole mapping again without inheriting the mapping from the mapped superclass. In such case, your entity should extend directly from FOS\UserBundle\Model\User (and FOS\UserBundle\Model\Group for the group).
It looks like doctrine 2.3 added these features. Unfortunately, SO decided that I had to duplicate information already present on their site to avoid a "trivial answer".
#AssociationOverride and #AttributeOverride in new Doctrine 2.3

Resources