How to override SonataUser entities properties and forms? - php

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.

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.

Create new content using Sonata Admin when mapped entity is an abstract class

I am trying to create new content using Sonata Admin, however due the entity is an abstract class I am getting on screen a new panel with title Select object type and the content has a blue box that says No object types available.
I don't know what kind of settings I need to set-up in order to be able to select and create one of the entities that are extending my abstract class.
Any help will be more than welcomed!
AppBundle\Entity\AbstractAlert
/**
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(
* name="dtype",
* type="string"
* )
* #ORM\DiscriminatorMap({
* "email" = "AppBundle\Entity\EmailAlert",
* "sms" = "AppBundle\Entity\SmsAlert"
* })
*/
abstract class AbstractAlert
{
}
AppBundle\Entity\EmailAlert
class EmailAlert extends AbstractAlert
{
}
AppBundle\Entity\SmsAlert
class SmsAlert extends AbstractAlert
{
}
SonataAdminBundle\Admin\AlertAdmin
class MassiveAlertAdmin extends AbstractAdmin
{
protected function configureFormFields(FormMapper $form)
{
$form
->with('panel name')
->add('fieldName')
->end();
}
}
This is how it looks my Sonata Admin => Create page
If any of you can give me a clue please, I will appreciate it.
Thanks in advance for your help,
Ok, i was wrong and finally found a solution for you ... you have only to choose the abstract entity as you already got and set subclasses via DI as shown here in 16.3 https://sonata-project.org/bundles/admin/2-1/doc/reference/advance.html ... that works like a charm and you'll get your choices in the add button! If not, i could imagine, that every concrete entity class also must have a own admin services, my classes already does.
And for me: learning never stops ... Sorry for my wrong answer in the previous post ... having this knowledge helps me also now, improving my code. Thanks for that.
From 11/2018, i have the same problem but i use yaml to the config of the services.
The class 'Operation' is the abstract one.
Here is a example with yaml
app.admin.operation:
class: App\Admin\OperationAdmin
arguments: [~, App\Entity\Operation, ~]
tags:
- { name: sonata.admin, manager_type: orm, group: "app.admin.group.operation", label: Operation }
calls:
- [ setSubClasses, [{ TeamOperation: App\Entity\TeamOperation, Staff: App\Entity\StaffOperation, PlayersOperation: App\Entity\PlayersOperation} ]]
Whit this type of configuration you can select the type of 'Operation' between that 3 entities.

Override Users Form Sonata UserBundle

Hello I tried to follow the explanation given here:
How to remove fields from the admin user create page of Sonata User Bundle?
To add / remove lines to the Sonata Userbundle registration form but the problem is that I find myself for this error:
Here are the few lines of code that I put:
in src/Application/Sonata/userBundle/Admin/UserAdmin.php
use Sonata\UserBundle\Admin\Model\UserAdmin as BaseUserAdmin;
use Sonata\AdminBundle\Form\FormMapper;
class UserAdmin extends BaseUserAdmin {
protected function configureFormFields( FormMapper $formMapper ) {
parent::configureFormFields($formMapper);
$formMapper
->remove('facebookUid');
}
}
In
app/config/config.php :
sonata_user:
security_acl: true
manager_type: orm
admin: # Admin Classes
user:
class: Application\Sonata\UserBundle\Admin\UserAdmin
controller: SonataAdminBundle:CRUD
Can someone tell me why? Thank you
You have forgotten to declare the namespace of your UserAdmin class.
It has to be the very first line of code, and will be like that in your case:
<?php
namespace UserBundle\Admin;
use Sonata\UserBundle\Admin\Model\UserAdmin as BaseUserAdmin;
use Sonata\AdminBundle\Form\FormMapper;
class UserAdmin extends BaseUserAdmin {

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

Extending Sonata User Bundle and adding new fields [closed]

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.

Categories