Extending Sonata User Bundle and adding new fields [closed] - php

It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 9 years ago.
I am extending the Sonata User Bundle and creating some extra fields in the new user entity. These fields will only be updated within the Sonata admin area under users so they do not need to be available in the edit profile form. I am having trouble updating these fields via the Sonata User Manager and tried several different ways to extend/implement that class in Application\Sonata\UserBundle. Has anyone encountered this before and can give me a tutorial or step by step process of the cleanest way to extend the new User entity?

1. Create a new bundle
Something like AcmeUserBundle. Create it and register it as you do normally.
2. Create a new User entity
Then create a User and Group entity which extends Sonata\UserBundle\Entity\BaseUser and Sonata\UserBundle\Entity\BaseGroup. You should also add the configuration for the primary key, for instance:
/**
* #ORM\Entity
* #ORM\Table(name="fos_user")
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
}
3. Configure the entity
then, go to your app/config/config.yml file and configure these new entities:
sonata_user:
class:
user: Acme\UserBundle\Entity\User
group: Acme\UserBundle\Entity\Group
4. Override the UserAdmin class
Then, you need to create a new UserAdmin class. To do this, just create a new UserAdmin class inside your bundle, extend Sonata\UserBundle\Admin\Model\UserAdmin and override the methods like this:
namespace Acme\UserBundle\Admin;
use Sonata\UserBundle\Admin\Model\UserAdmin as SonataUserAdmin;
class UserAdmin extends SonataUserAdmin
{
/**
* {#inheritdoc}
*/
protected function configureFormFields(FormMapper $formMapper)
{
parent::configureFormFields($formMapper);
$formMapper
->with('new_section')
->add(...)
// ...
->end()
;
}
}
5. Replace the old UserAdmin class
Then, you need to make sure Sonata uses the new UserAdmin class. You just need to set the sonata.user.admin.user.class parameter to your new class and your ready!
# app/config/config.yml
parameters:
sonata.user.admin.user.class: Acme\UserBundle\Admin\UserAdmin

I found out the issue was a doctrine issue. My extended bundle was utilizing the original xml field mappings. I deleted those files and reverted to annotations. Everything worked brilliantly from there. I hope this helps someone else who is experiencing the same issue.

This is easy, yet the SonataUserBundle documentation is pretty short on this. Basically, after setting up the two bundles as described here and here:
You need to create a class to extend the Sonata\UserBundle\Entity\BaseUser class in SonataUserBundle. Note that if you override the constructor, you still must call the parent object's constructor.
namespace Your\Bundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\EntityManager;
use Sonata\UserBundle\Entity\BaseUser as BaseUser;
/**
* #ORM\Entity
* #ORM\Table(name="user",indexes={#ORM\Index(name="username_idx", columns={"username"})})
*/
class User extends BaseUser {
public function __construct()
{
parent::__construct();
// your code here
}
/**
* #ORM\Column(type="string")
*/
protected $firstName = "";
public function getFirstName() {
return $this->firstName;
}
public function setFirstName($firstName) {
$this->firstName = $firstName;
}
}
If you need to, you can also override the Sonata\UserBundle\Entity\BaseGroup object in a similar way.
Then, edit your config.yml to match your namespaces, like this
# FOS User Bundle Configuration
fos_user:
user_class: Your\Bundle\Entity\User
# To also override the Group object
# group:
# group_class: Your\Bundle\Entity\Group
# Sonata User Bundle configuration
sonata_user:
class:
user: Your\Bundle\Entity\User
# To also override the Group object
# group: Your\Bundle\Entity\Group
Clear the cache. Your entities will be used instead of the built-in ones.

Related

Symfony - inject doctrine repository in service

according to How to inject a repository into a service in Symfony2?
it's like
acme.custom_repository:
class: Doctrine\ORM\EntityRepository
factory: ['#doctrine.orm.entity_manager', getRepository]
arguments:
- 'Acme\FileBundle\Model\File'
but I get an Exception
Invalid service "acme.custom_repository": class
"EntityManager5aa02de170f88_546a8d27f194334ee012bfe64f629947b07e4919__CG__\Doctrine\ORM\EntityManager"
does not exist.
How can I do this in Symfony 3.4?
update:
EntityClass is actually a valid class FQCN (also used copy reference on phpstorm to be sure) , just renamed it because a companies name is in it :). updated it anyway.
solution
BlueM's solution works perfectly.
In case you are not using autowiring here's the service defintion:
Acme\AcmeBundle\Respository\MyEntityRepository:
arguments:
- '#Doctrine\Common\Persistence\ManagerRegistry'
- Acme\AcmeBundle\Model\MyEntity # '%my_entity_class_parameter%'
As you are using Symfony 3.4, you can use a much simpler approach, using ServiceEntityRepository. Simply implement your repository, let it extend class ServiceEntityRepository and you can simply inject it. (At least when using autowiring – I haven’t used this with classic DI configuration, but would assume it should also work.)
In other words:
namespace App\Repository;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Common\Persistence\ManagerRegistry;
class ExampleRepository extends ServiceEntityRepository
{
/**
* #param ManagerRegistry $managerRegistry
*/
public function __construct(ManagerRegistry $managerRegistry)
{
parent::__construct($managerRegistry, YourEntity::class);
}
}
Now, without any DI configuration, you can inject the repository wherever you want, including controller methods.
One caveat (which equally applies to the way you try to inject the repository): if the Doctrine connection is reset, you will have a reference to a stale repository. But IMHO, this is a risk I accept, as otherwise I won’t be able to inject the repository directly..
Create the custom repository properly
First, you need to create the repository custom class that extends the default repository from doctrine:
use Doctrine\ORM\EntityRepository;
class UserRepository extends EntityRepository
{
// your own methods
}
Then you need this annotation in the entity class:
/**
* #ORM\Entity(repositoryClass="MyDomain\Model\UserRepository")
*/
Then you define the repository in the .yml file:
custom_repository:
class: MyDomain\Model\UserRepository
factory: ["#doctrine", getRepository]
arguments:
- Acme\FileBundle\Model\File
Make sure that in the definition of your repository class points to your custom repository class and not to Doctrine\ORM\EntityRepository.
Inject custom services into your custom repository:
On your custom repository create custom setters for your services
use Doctrine\ORM\EntityRepository;
class UserRepository extends EntityRepository
{
protected $paginator;
public function setPaginator(PaginatorInterface $paginator)
{
$this->paginator = $paginator;
}
}
Then inject them like this:
custom_repository:
class: MyDomain\Model\UserRepository
factory: ["#doctrine", getRepository]
arguments:
- Acme\FileBundle\Model\File
calls:
- [setPaginator, ['#knp_paginator']]
Inject your repository into a service:
my_custom_service:
class: Acme\FileBundle\Services\CustomService
arguments:
- "#custom_repository"
Check the arguments is a valid class (with FQCN or with a bundle simplification) as example:
acme.custom_repository:
class: Doctrine\ORM\EntityRepository
factory:
- '#doctrine.orm.entity_manager'
- getRepository
arguments:
- Acme\MainBundle\Entity\MyEntity
or
acme.custom_repository:
class: Doctrine\ORM\EntityRepository
factory:
- '#doctrine.orm.entity_manager'
- getRepository
arguments:
- AcmeMainBundle:MyEntity
Hope this help
Solutions I could see here so far are not bad. I looked at it from a different angle. So my solution allows you to keep clean repositories, sorta enforces consistent project structure and you get to keep autowiring!
This is how I would solve it in Symfony 5.
GOAL
We want to have autowired Repositories and we want to keep them as clean as possible. We also want them to be super easy to use.
PROBLEM
We need to figure out a way to tell Repository about the entity it should use.
SOLUTION
The solution is simple and consists of a few things:
We have custom Repository class which extends Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository class.
Our custom class has public string $entity property on it.
When we create our new repository and extend our custom repository class we have two choices: on our new repository we can just point to the class like this
namespace App\Database\Repository\Post;
use App\Database\Repository\Repository;
use App\Entity\Blog\Post;
/**
* Class PostRepository
* #package App\Database\Repository
*/
class PostRepository extends Repository
{
public string $entity = Post::class;
public function test()
{
dd(99999, $this->getEntityName());
}
}
or we could omit that property and let our new base Repository class find it automatically! (More about that later.)
CODE
So let's start with the code and then I will explain it:
<?php
namespace App\Database\Repository;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use Laminas\Code\Reflection\ClassReflection;
use Symfony\Component\Finder\Finder;
/**
* Class Repository
* #package App\Database\Repository
*/
abstract class Repository extends ServiceEntityRepository
{
/** #var string */
private const REPOSITORY_FILE = 'repository';
/** #var string */
public string $entity = '';
/** #var string */
public string $defaultEntitiesLocation;
/** #var string */
public string $defaultEntitiesNamespace;
/**
* Repository constructor.
*
* #param ManagerRegistry $registry
* #param $defaultEntitiesLocation
* #param $defaultEntitiesNamespace
* #throws \Exception
*/
public function __construct(
ManagerRegistry $registry,
$defaultEntitiesLocation,
$defaultEntitiesNamespace
) {
$this->defaultEntitiesLocation = $defaultEntitiesLocation;
$this->defaultEntitiesNamespace = $defaultEntitiesNamespace;
$this->findEntities();
parent::__construct($registry, $this->entity);
}
/**
* Find entities.
*
* #return bool
* #throws \ReflectionException
*/
public function findEntities()
{
if (class_exists($this->entity)) {
return true;
}
$repositoryReflection = (new ClassReflection($this));
$repositoryName = strtolower(preg_replace('/Repository/', '', $repositoryReflection->getShortName()));
$finder = new Finder();
if ($finder->files()->in($this->defaultEntitiesLocation)->hasResults()) {
foreach ($finder as $file) {
if (strtolower($file->getFilenameWithoutExtension()) === $repositoryName) {
if (!empty($this->entity)) {
throw new \Exception('Entity can\'t be matched automatically. It looks like there is' .
' more than one ' . $file->getFilenameWithoutExtension() . ' entity. Please use $entity
property on your repository to provide entity you want to use.');
}
$namespacePart = preg_replace(
'#' . $this->defaultEntitiesLocation . '#',
'',
$file->getPath() . '/' . $file->getFilenameWithoutExtension()
);
$this->entity = $this->defaultEntitiesNamespace . preg_replace('#/#', '\\', $namespacePart);
}
}
}
}
}
Ok, so what is happening here? I have bound some values to the container in services.yml:
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
bind:
$defaultEntitiesLocation: '%kernel.project_dir%/src/Entity'
$defaultEntitiesNamespace: 'App\Entity'
Then in our new extension class, I know where by default to look for my Entities (this enforces some consistency).
VERY IMPORTANT BIT - I assume that we will name Repositories and Entities with exactly the same so for example: Post will be our Entity and PostRepository is our repository. Just note that the word Repository is not obligatory. If it is there it will be removed.
Some clever logic will create namespaces for you - I assume that you will follow some good practices and that it will all be consistent.
It's done! To have your repository autowired all you need to do is extend your new base repository class and name Entity the same as the repository. so End result looks like this:
<?php
namespace App\Database\Repository\Post;
use App\Database\Repository\Repository;
use App\Entity\Blog\Post;
/**
* Class PostRepository
* #package App\Database\Repository
*/
class PostRepository extends Repository
{
public function test()
{
dd(99999, $this->getEntityName());
}
}
It is CLEAN, AUTOWIRED, SUPER EASY AND QUICK TO CREATE!
What about the drawbacks about the ServiceEntityRepository?
https://symfony.com/doc/current/doctrine/multiple_entity_managers.html
One entity can be managed by more than one entity manager. This
however results in unexpected behavior when extending from
ServiceEntityRepository in your custom repository. The
ServiceEntityRepository always uses the configured entity manager for
that entity.
In order to fix this situation, extend EntityRepository instead and no
longer rely on autowiring:
In an own project I've seen that using:
$repository = $entityManager->getRepository(MyEntity:class)
The $repository->_em is not equals to $entityManager (with both using the same connection), causing problems like:
$entity = $entityManager->getRepository(MyEntity:class)->find($id);
$entityManager->refresh($entity); // throws 'entity is not managed'
That's why the entity is fetched with $repository->_em and the refresh (or persist, flush, etc.) is using $entityManager.
This problem is described here:
https://github.com/symfony/symfony-docs/issues/9878
So... You can't rely in ServiceEntityRepository using multiple entity managers, but the EntityRepository doesn't allow autowire, so, what?
My two cents (I believe this should be works in every scenario):
Manually set the class metadata (something like you need to do in the constructor of the ServiceEntityManager), so I can:
Remove the autowire of repositories in services.yaml:
App\:
resource: '../src/*'
exclude: '../src/{Entity,Migrations,Repository,Tests,Kernel.php,Client}'
(you also add the repositories below in services.yaml)
And create another /config/packages/repositories.yaml and add:
my.entity.metadata:
class: App\Entity\Metadata
arguments:
$entityName: App\Entity\MyEntity
App\Repository\MyEntityRepository:
arguments:
[$class: my.entity.metadata]
Now you have a EntityRepository that is capable of being autowireable. You can make a repositories.yaml file in the config and keep updated when you create/edit/delete your repositories. Is not cleanest but it should be works.

Symfony2: Entity extends MappedSuperClass extends FOSUserBundle User

I created a BaseBundle with a SuperUser MappedSuperClass who extends FOSUserBundle User
use FOS\UserBundle\Model\User as FOSUser;
/*
* #ORM\MappedSuperclass
*/
class SuperUser extends FOSUser
{
/**
* #var string
* #ORM\Column(name="locale", type="string", nullable=true)
*/
protected $locale;
}
In my Project I installed my BaseBundle. I created a User entity who extends my SuperUser class.
/*
* User class
*/
class User extends SuperUser
{
/**
* #var string
*/
private $fullName;
}
The problem is that when I create the table, Doctrine see only FOSUser properties and User properties but not my SuperUser properties. I can see only fullName and FOSUser properties.
My SuperUser class is bypassed..
I noticed that User class is with YAML notation, SuperUser is with PHP Annotation while FOSUser Class is with XML notation. I don't know if this create problems.
From the documentation:
A bundle can accept only one metadata definition format. For example, it's not possible to mix YAML metadata definitions with annotated PHP entity class definitions.
Try to add the YML mapping of your MappedSuperClass

How to override SonataUser entities properties and forms?

I am setting up a brand new project using Symfony 2.7.x and Sonata Admin 2.3.x plus Sonata User. By default Sonata add a bunch of useless fields and I want to keep my entity as clean as possible. So my first question is:
It's possible to override Sonata User entities in order to get ride of some useless properties? How?
Now as second part of the question and related to the same I want to create or use my own form for add new Users and/or Groups because with defaults ones I can't add roles. See the image below to see what I am talking about:
I should be able to add new dynamic roles from there and I can't.
Is this possible? How? Any workaround?
I took a look at Github here and Docs here but couldn't find nothing helpful. Any advices?
You can get rid of the Sonata properties by extending the FOSUserBundle entity directly rather than SonataUser User model.
change the entity that your User extends actually:
use Sonata\UserBundle\Model\User as BaseUser;
to the following:
use FOS\UserBundle\Entity\User as BaseUser;
Then, to delete useless properties from forms and maybe add new, override the default sonata UserAdmin class:
1- Create an admin class called UserAdmin in your own bundle.
2- Open the file vendor/sonata-project/user-bundle/Admin/Model/UserAdmin.php and take the configureFormFields from it. Paste it in your own admin class and keep only the fields you need by deleting useless fields from the base form builder.
The class can looks like:
use FOS\UserBundle\Model\UserManagerInterface;
use Sonata\AdminBundle\Admin\Admin;
use Sonata\AdminBundle\Form\FormMapper;
class UserAdmin extends Admin // You can extends directly from SonataUserAdmin if it's easier for you
{
protected $userManager;
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
... The fields you keep ...
}
/**
* #param UserManagerInterface $userManager
*/
public function setUserManager(UserManagerInterface $userManager)
{
$this->userManager = $userManager;
}
/**
* #return UserManagerInterface
*/
public function getUserManager()
{
return $this->userManager;
}
}
3- Define the new UserAdmin class as a service
services:
sonata.user.admin.user:
class: YourOwnAdminBundle\Admin\UserAdmin
tags:
- { name: sonata.admin, manager_type: orm, group: %sonata.user.admin.groupname%, label: "User", label_catalogue: %sonata.user.admin.label_catalogue%", icon: "%sonata.user.admin.groupicon%"}
arguments:
- ~
- %sonata.user.admin.user.entity%
- %sonata.user.admin.user.controller%
calls:
- [setUserManager, ["#fos_user.user_manager"]]
- [setTranslationDomain, ["%sonata.user.admin.user.translation_domain%"]]
Then, adapt the configuration of sonata-user in config.yml:
sonata_user:
...
admin:
user:
class: YourOwnAdminBundle\Admin\UserAdmin
controller: SonataAdminBundle:CRUD
translation: SonataUserBundle
And it should be good.
Look at this similar question in case of I forgotten something or you need more.

Symfony Annotations are not parsed

I have installed the FOSUserBundle and installed it as per its detailed installation guide (https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Resources/doc/index.md).
When i run php console doctrine:schema:update --force for the first time, it populates the users table with all of the default fields that the FOSUserBundle has defined.
Unfortunately it appears to be completely missing the fields which i have added to my user entity and i am wondering if its utilising the configuration file which is specified in the installation guide instead of using the annotations which are in the entity.
It also appears to be ignoring the other entities within the same Bundle.
namespace Acme\UserBundle\Entity;
use FOS\UserBundle\Entity\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="users")
*/
class User extends BaseUser {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="integer")
* #ORM\ManyToOne(targetEntity="Bureau", mappedBy="id")
*/
protected $bureau;
public function __construct() {
parent::__construct();
}
}
This is my user entity, of which bureau is being completely ignored regardless if it has a relationship or not.
Edit
As per requested, please find below the orm config file. It's the default file as per the configuration.
I have suspected this to be the problem, but i wasnt sure if annotations and the config file could work together.
Acme\UserBundle\Entity\User:
type: entity
table: users
id:
id:
type: integer
generator:
strategy: AUTO
Edit 2
I have found that if i remove the orm configuration file that it all magically works again!!
So i would adjust my question for clarity.
Updated question
If an orm configuration file exists, are annotations ignored?
When you generate entities with the console, you are asked on the format, which is:
xml
yaml
annotations
Regardless of what you choose, there are no signifiers telling Doctrine which to use besides the fact one exists. In order, YAML takes priority over annotations, and so it should.

FosUserBundle don't persist data in mongodb database

I use symfony2 and the mongoDb ODM. Today I have installed FosUserBundle.
My User class is like that :
use FOS\UserBundle\Document\User as BaseUser;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
/**
* #MongoDB\Document
*/
class User extends BaseUser
{
/**
* #MongoDB\Id(strategy="auto")
*/
protected $id;
public function __construct()
{
parent::__construct();
}
}
My problem is after created a User with FosUserBundle create command, only the id of user is persisted in mongodb document.
If I add the following in my User class :
/**
* #MongoDB\String
*/
protected $username;
The create command persist Id and the good username.
Of course, it's the same with all the initial fields of FOS\UserBundle\Document\User (BaseUser).
It looks like the inheritance mapping is not working properly.
Check that your Doctrine configuration in config.yml is set to:
auto_mapping: true
To work around this another way you would need to add the complete mapping information to your User entity extending the one from the FOSUserBundle.
With Doctrine\ORM it is normally the #ORM\MappedSuperClass annotation which provides the mapping for the extending class. In FOSUserBundle's mongodb xml mapping it is this line:
...
<mapped-superclass name="FOS\UserBundle\Document\User" collection="fos_user_user">
...
Solution 1:
Try this:
copy the mapping xml from FOSUserBundle over to your UserBundle into Resources/config/doctrine/User.mongogb.xml
change it to fit your own Entity Class
remove mapped-superclass node
add the id field as Id with auto strategy
You can then ommit the #MongoDB annotations on your entity completely.
To save somebody's time.
I also came across the issue of non-working mapping for ODM User entity and could not understand why it doesn't work. The reason was I must have extended my user entity from FOS\UserBundle\Document\User and not from FOS\UserBundle\Model\User

Categories