Closed tdumalin closed 2 years ago
Hello. I did something like this in my project. I have a "BaseAdmin" class:
namespace App\Admin;
use Psr\Container\ContainerInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
/**
* Handles base admin methods.
*
* Must be extended by every admin class.
*/
abstract class BaseAdmin extends AbstractAdmin implements ServiceSubscriberInterface
{
/**
* Global services for all Admin classes.
*/
private const GLOBAL_SERVICES = [
ParameterBagInterface::class,
Security::class,
];
/**
* A small DI container.
*/
private ContainerInterface $container;
public function __construct($code, $class, $baseControllerName = null, ContainerInterface $container)
{
$this->container = $container;
parent::__construct($code, $class, $baseControllerName);
}
final public static function getSubscribedServices()
{
return array_unique(array_merge(static::registerServices(), self::GLOBAL_SERVICES));
}
/**
* You can override this method in Admin class and register own services.
* Registered services you can fetch via "getContainer" method.
*/
public static function registerServices(): array
{
return [];
}
/**
* Returns small DI container.
*/
public function getContainer(): ContainerInterface
{
return $this->container;
}
/**
* Returns container parameter.
*
* @param mixed|null $default
*
* @return mixed|null
*/
public function getContainerParameter(string $parameter, $default = null)
{
if ($this->getContainer()->get(ParameterBagInterface::class)->has($parameter)) {
return $this->getContainer()->get(ParameterBagInterface::class)->get($parameter);
}
return $default;
}
}
And in some Admin class:
namespace App\Admin\User;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
class UserAdmin extends BaseAdmin
{
public static function registerServices(): array
{
return [
UserPasswordEncoderInterface::class,
];
}
/**
* Updates user password.
*/
private function updateUserPassword(User $user): void
{
if (!$user->getPlainPassword()) {
return;
}
$passwordEncoder = $this->getContainer()->get(UserPasswordEncoderInterface::class);
$user->setPassword($passwordEncoder->encodePassword($user, $user->getPlainPassword()));
$user->eraseCredentials();
}
}
I believe that in the future will be added full support for autowiring and you can inject needed service in the constructor.
Hi @mleko64, That's exactly what i need, I didn't know that adding the container as last construct arguments will be autowired. Thanks for the share !
It'd be nice to have this by default in v4.0. In recent symfony version autowiring is the recommended way. Both behavior can exist, and it could be a flag in admin generation command. Would the maintainers be interested in this? If so I can make a PR.
Sure! I don't know if is going to make it to 4.0 or 5.0, but any help is welcomed. I thought about this some time ago and I wanted to revisit this, I'll throw some random ideas (haven't tried, so maybe ) I had just in case it helps:
One of the problems is to get rid of the __constructor
of AbstractAdmin
, right now there are 3 parameters: $code
, $class
and $baseControllerName
.
I think we can get rid of $baseControllerName
"easily", apparently it is only used in AbstractAdmin::buildRoutes
, here:
So one way to remove this is to create a RouteBuilderInterface::create(AdminInterface $admin)
method that would return the RouteCollection
and that service would be in charge of retrieving the correct baseControllerName
from a ControllerRegistry
with a ControllerRegistry::get(AdminInterface $admin)
that would return the proper baseControllerName
for an Admin.
The $class
argument is fine to me, we can keep it and for the $code
one... we can create a method like initialize
that the Compiler Pass would call it with the $code
parameter (the id of the service).
After this, I thought about creating an abstract class, similar to the suggested here, something like:
<?php
//...
abstract class ServiceAbstractAdmin extends AbstractAdmin implements ServiceSubscriberInterface
{
public function __construct(string $modelClass)
{
parent::__construct($modelClass);
}
/**
* @internal
*/
public function setContainer(ContainerInterface $container): void
{
$this->container = $container;
}
public static function getSubscribedServices(): array
{
return [
// Here will go the persistence agnostic services like translator, validator, pool, security_handler, etc
];
}
}
and then in the persistence bundles, create another abstract admin extending from this one and adding the persistence dependent services, so at the end, the user would extend from those admin classes.
As I said, this is some random thoughts I haven't tried, just thinking out loud.
Sure! I don't know if is going to make it to 4.0 or 5.0
The 4.0 milestone is almost finished and I think that we could try release the 4.0 version in January or February, so I would recommend the 5.0 milestone. The priority is to offer a proper Symfony 5 support ASAP, and this feature seems to introduce a lot of refactoring.
But any PR is welcomed.
There's two features being discussed here, one is make Admin support ServiceSubscriberInterface and 2nd is configure admin directly in file.
I'd like to implement configure admin directly in file.
first.
@franmomu I was thinking of doing more in heavy lifting in Compiler Pass and leaving Admin as it is. We make new interface, ex: AutoconfigurableAdmin
. If a service implements AutoconfigurableAdmin
compiler pass will call its static methods and configure it.
What do you think?
Be aware that there is more configuration keys: https://github.com/sonata-project/SonataAdminBundle/blob/3.x/src/DependencyInjection/Compiler/AddDependencyCallsCompilerPass.php#L228-L241
Also, @wbloszyk is introducing another CompilerPass https://github.com/sonata-project/SonataAdminBundle/pull/6566. We should verify that your idea is compatible. (If we want to configure from the class, we should be able to configure everything)
Be aware that there is more configuration keys
If we want to configure from the class, we should be able to configure everything
yes, that makes sense. A static method for every attribute would be ugly AF. So these must be condensed into one function. But the constructor argument is staying same three vars. So I think
interface AutoconfigurableAdmin
{
public static function getCode();
public static function getEntityClass();
public static function getControllerClass();
public static function getAttributes();
}
getAttributes
will return an array, others will return just a value.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
One thing for Thought:
Annotation are deprecated/archived.
For autoconfigure, an order option could be added.
Be aware that there is more configuration keys
If we want to configure from the class, we should be able to configure everything
yes, that makes sense. A static method for every attribute would be ugly AF. So these must be condensed into one function. But the constructor argument is staying same three vars. So I think
interface AutoconfigurableAdmin { public static function getCode(); public static function getEntityClass(); public static function getControllerClass(); public static function getAttributes(); }
getAttributes
will return an array, others will return just a value.
Hey @sarim just to remember that constructor parameters are deprecated and it should be passed in services tags
admin.category:
class: App\Admin\CategoryAdmin
tags:
- { name: sonata.admin, model_class: App\Entity\Category, controller: ~, manager_type: orm, label: Category }
Annotation are deprecated/archived.
That's not completely true. I forked @kunicmarko20's project and replaced annotations with attributes: https://github.com/nucleos/SonataAutoConfigureBundle
Annotation are deprecated/archived.
That's not completely true. I forked @kunicmarko20's project and replaced annotations with attributes: https://github.com/nucleos/SonataAutoConfigureBundle
does your fork has the ability to order the Sections in the Sonata Admin Menu, or would need that more changes for this bundle?
does your fork has the ability to order the Sections in the Sonata Admin Menu, or would need that more changes for this bundle?
That's not possible at the moment, because the original project does not provide this feature and I don't depend on the order.
Feed free to port this feature to the sonata bundle or provide a priority
feature.
well I guess this issue could be closed?!
right @tdumalin ?
I created this ticket to add the tip in the docs: #7820
@eerison , yes this issue is related to the PR. Great news for the docs, hope it will be useful !
Feature Request
Use the ServiceSubscriberInterface and some static function to autoconfigure admins, so no need to us a xml/yaml/php file to configure admin, Here is an example of the idea:
The configure all required stuff for an admin: