Export user roles with Sonata admin - symfony

I'm using SonataAdmin and FosUserBundle with Symfony 4.
I want to use the export feature to export whole users' data in CSV, JSON ...
When a trigger the export, the roles column in the file is empty or null.
In the UserAdmin class, I have overridden the getExportFields function with the call of a specific method to get the role as explained in this post. Sonata admin export fields with collection fields
But it doesn't work.
Example in my case:
public function getExportFields()
{
return [
'id',
'username',
'roles' => 'rolesExported'
];
}
And in my User Entity:
public function getRolesExported()
{
$exportedRoles = [];
foreach ($this->getRealRoles() as $role) {
$exportedRoles[] = $role->__toString();
}
return $this->rolesExported = implode(' - ', $exportedRoles);
}
In this case, when I trigger the export, my web browser shows the error
'website is inaccessible' with no error in the dev.log.
When I delete 'roles' => 'rolesExported' in the getExportFields function, the export is well triggered.
SonataAdmin version : 3.35
FosUserBundle version : 2.1.2
Symfony version: 4.3.2 (I know that I need to update it)

I suspect that the __toString() call causes a problem.
although the post you used as your inspiration explicitly says that it wants to export collections, I assume you might want to export an array.
Since I don't know the type of your $role objects, for debugging purposes first replace $role->__toString() with gettype($role), so the line is:
$exportedRoles[] = gettype($role);
I see three cases here:
object or for multiple roles object - object - ..., in that case, you should select a method of Role that returns a proper string or create one at that place, like $exportedRoles[] = $role->getName();
string or for multiple roles string - string - ..., in that case your "real" roles is just an array, and you can replace the contents of your function with return implode(' - ', $this->getRealRoles());
array or for multiple roles array - array - ..., in that case you have an array for each role, and those don't provide __toString. Select a method to construct the exported role, like $exportedRoles[] = $role['name'];

Related

What is the best way to create a singleton entity in Symfony 4?

I want to create a settings page, which only has a form in it. If the form is submitted it only updates settings entity but never creates another one. Currently, I achieved this like:
/**
* #param SettingsRepository $settingsRepository
* #return Settings
*/
public function getEntity(SettingsRepository $settingsRepository): Settings
{
$settings = $settingsRepository->find(1);
if($settings == null)
{
$settings = new Settings();
}
return $settings;
}
In SettingsController I call getEntity() method which returns new Settings entity (if the setting were not set yet) or already existing Settings entity (if setting were set at least once).
However my solution is quite ugly and it has hardcoded entity id "1", so I'm looking for a better solution.
Settings controller:
public function index(
Request $request,
SettingsRepository $settingsRepository,
FlashBagInterface $flashBag,
TranslatorInterface $translator,
SettingsService $settingsService
): Response
{
// getEntity() method above
$settings = $settingsService->getEntity($settingsRepository);
$settingsForm = $this->createForm(SettingsType::class, $settings);
$settingsForm->handleRequest($request);
if ($settingsForm->isSubmitted() && $settingsForm->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($settings);
$em->flush();
return $this->redirectToRoute('app_admin_settings_index');
}
return $this->render(
'admin/settings/index.html.twig',
[
'settings_form' => $settingsForm->createView(),
]
);
}
You could use Doctrine Embeddables here.
Settings, strictly speaking, should not be mapped to entities, since they are not identifiable, nor meant to be. That is, of course, a matter of debate. Really, a Settings object is more of a value object than an entity. Read here for more info.
So, in cases like these better than having a one to one relationship and all that fuzz, you probably will be fine with a simple Value Object called settings, that will be mapped to the database as a Doctrine Embeddable.
You can make this object a singleton by creating instances of it only in factory methods, making the constructor private, preventing cloning and all that. Usually, it is enough only making it immutable, meaning, no behavior can alter it's state. If you need to mutate it, then the method responsible for that should create a new instance of it.
You can have a a method like this Settings::createFromArray() and antoher called Settings::createDefaults() that you will use when you new up an entity: always default config.
Then, the setSettings method on your entity receieves only a settings object as an argument.
If you don't like inmutablity, you can also make setter methods for the Settings object.

Sonata Admin Bundle: for create new, how can I have user already selected?

I have two (2) Admins, UserAdmin and CarAdmin. From the list view of UserAdmin, I want to have a custom action that redirects to CarAdmin create view with the user already selected.
So far I have managed to create a custom action with its controller. My challenge is to redirect to CarAdmin create/new form passing some parameters for data persistence.
Any points of reference will be much appreciated.
Thanks
Sonata Admin allows to resolve such task pretty easy, no need to make a custom action. One of the solutions could be:
Define a custom template for one column in UserAdmin list view, render a special button(link) in it. A link should lead to CarAdmin create action with some get parameter.
In CarAdmin in method getNewInstance() check that if there is a special get parameter - set user with that ID. This step can also be done in methods getFormFields(), prePersist(), etc.
Some code samples:
In UserAdmin
protected function configureListFields(ListMapper $listMapper)
{
$listMapper
->add('actions', 'string', array(
'template' => 'your_template_name.html.twig',
'mapped' => false,
)
);
}
In your_template_name.html.twig
Create Car for this user
In CarAdmin
public function getNewInstance()
{
$car= parent::getNewInstance();
$userId = $this->getRequest()->query->get('user');
if ($userId) {
$em = $this->modelManager->getEntityManager(User::class);
$user = $em->getRepository(User::class)->find($id);
$car->setUser($user);
}
return $car;
}

Pass current variable into custom faker in Alice Nelmio Bundle Fixtures 2.x

I've created a fixtures loader class in Symfony with custom fakers.
One of this custom functions should return a name from an array of names depending on a non-random value. I'd like this value to be the $current variable when creating a collection of entities with Alice 2.x
As we can do something like this in the fixtures.yml file:
user{1..10}:
name: someName<current()>
I would like to pass that current value in to my custom function like so:
user{1..10}:
name: pickFromArray($current)
I've tried $current, current(), , , ....
without success.
Thanks!
You need to use the returned value of <current()> function as parameter. Tested on hautelook/AliceBundle.
user{1..10}:
name: <pickFromArray(<current()>)>
I don't think you can do that.
In my opinion, the best option is pick the name in the Processor:
public function postProcess($object)
{
if (!$object instanceof User) {
return false;
}
$object->setName($this->pickFromArray($object));
return true;
}
Where pickFromArray() is a method in your Processor.

How to manually set a property in Sonata Admin ListMapper from database

My model has a "parent" entity called Application and a "child" entity called "role" which joins to application via a ManyToOne relationship.
However, Application does not have an inverse "oneToMany" relationship to Role for performance reasons.
What I'm trying to do is to join a role entity to the application entity on the Application's Sonata Admin List.
The closest I've been able to come is using createQuery on the applicationAdmin class like so:
public function createQuery($context = 'list'){
$query = parent::createQuery($context);
$query
->leftJoin('fnBundle:Role','r','WITH','r.application = ' . $query->getRootAlias() . '.id')
->leftJoin('fnBundle:User','u','WITH','ar.user = u')
->addSelect('u.email');
return $query;
}
This dies with the following error:
"Warning: get_class() expects parameter 1 to be object, array given")
in "SonataAdminBundle:CRUD:base_list_field.html.twig"
I believe (though I've had trouble proving) this is because the return application object is nested in an array with two keys like
array(0 => <Application Object>, 1 => <User Email>);
instead of just the application object.
So how would I go about fixing this? Can I select the u.email AS a property of the application (I don't think so, I've been looking for a solution)...should I override the DataGrid somehow, or is there no elegant solution to this problem?
Thanks!

Sonata Admin - how to set the menu.label attribute?

According to the Sonata source code, the last node in the breadcrumb is rendered this way:
# standard_layout.html.twig #
<li class="active"><span>{{ menu.label }}</span></li>
In my setup, when opening a given Admin subclass, the last node simply becomes a raw string according to the entity handled by the Admin:
Dashboard / Entity List / Acme\SomeBundle\Entity\Stuff:000000001d74ac0a00007ff2930a326f
How can I set the value of menu.label to get something more appropriate? I have tried, in my Admin subclass, to override the following:
protected function configureTabMenu(MenuItemInterface $menu, $action, AdminInterface $childAdmin = null) {
$this->configureSideMenu($menu, $action, $childAdmin);
}
protected function configureSideMenu(MenuItemInterface $menu, $action, AdminInterface $childAdmin = null) {
$menu->setLabel("Some nice label");
$menu->setName("Some nice name");
}
However, this does not change anything, even though I have verified that the methods above are called during runtime.
Finally found a good (and somewhat obvious) solution to this.
The Sonata Admin class uses an internal toString($object) method in order to get a label string for the entity it is handling. Thus, the key is to implement the __toString() method of the entity in question:
public function __toString() {
return "test";
}
The best way is to configure the $classnameLabel variable in the Admin Class :
class fooAdmin extends Admin
{
protected $classnameLabel = 'Custom Label';
}
But it have the same issue (weird name with entity path) doing it, even if it is working fine on all the others pages.
Apparently, the Sonata way of solving this is show here:
Quote:
While it’s very friendly of the SonataAdminBundle to notify the admin of a successful creation, the classname and some sort of hash aren’t really nice to read. This is the default string representation of an object in the SonataAdminBundle. You can change it by defining a toString() (note: no underscore prefix) method in the Admin class. This receives the object to transform to a string as the first parameter:
Source: https://sonata-project.org/bundles/admin/master/doc/getting_started/the_form_view.html#creating-a-blog-post

Resources