EasyCorp / EasyAdminBundle

EasyAdmin is a fast, beautiful and modern admin generator for Symfony applications.
MIT License
4.02k stars 1.02k forks source link

How can I use NON-crud controller? #3824

Closed vldmr-k closed 3 years ago

vldmr-k commented 3 years ago

Hi everyone I have remote service (ex. order microservice), how can I render items which I receving trought api endpoint such as EasyAdmin controller (with design, actions, layout and etc).

AbstractCrudController must have configurate entity (I don't have entity, I have array data from remote service). I treid use Symfony\Bundle\FrameworkBundle\Controller\AbstractController but many features of easyadmin does'nt work. I tried extens dashboard controller, but when I combine dashboard controller + crud controller, items menu has wrong urls.

For example /dashboard - i'ts DashboardController controller /remote-order/list - i'ts controller which I extends of DashboardController controller and make some of routes and action like as list, show etc.

When I visit url /remote-order/list, and try to click to the /dashboard some crud controller, I see that easyadmin generate url from /remote-order/list?crudAction........ but I want that EA3 generate url from /dashbaord?crudAction .....

It happend bcs easyadmin think that I want to use multi-dashboard application. I see that cash file var/cache/dev/easyadmin/routes-dashboard.php has many dashboard routes.

<?php return array (
  'admin' => 'App\\Controller\\Admin\\DashboardController::index',
  'admin.novaposhta.remote-document-list' => 'Barygi\\DeliveryNovaposhtaBundle\\Controller\\Admin\\NovaposhtaRemoteDocumentController::index',
  'admin.novaposhta.remote-sheet-list' => 'Barygi\\DeliveryNovaposhtaBundle\\Controller\\Admin\\NovaposhtaRemoteSheetController::index',
);

Is someone have any idea how to implement NON-crud controller with correcting works via EA3?

Thanks Vladimir

javiereguiluz commented 3 years ago

Hi! Sorry but this bundle doesn't provide any utility to help you in this case. It's only optimized for typical CRUD features. In some of my apps I have different things (e.g. info stored in a CSV file) and I implemented that feature manually (reusing the CSS styles, etc. but doing everything by hand). Sorry but there's not a better answer in this case.

edenreich commented 3 years ago

I know it's probably an old request, but any Intentions of making this bundle available for consuming a remote service ?

I'm facing the same issue, and I wish there was an easy way to just use datatables, to consume different responses from controller actions instead of relying on the same application as the single source of truth for data. This feature could be very useful in my opinion (for example imagine you have frontend and backend-office, and you want to share the same data between the two with extra privilegs to requests that are coming from the back-office) any plans any time soon on making it possible to override the list page at least ?

What i've already tried is to extend AbstractCrudController::index method and I had to disable the paginator because it runs a query on the database (SELECT DISTINCT articles.id), it complains that it can't find the table in it's own database but I can't seems to figure out how to pass the "custom" data to the view instead of using it's own database.

For all CRUD operations I figured out it is possible to override AbstractCrudController::updateEntity(), AbstractCrudController::persistEntity() and AbstractCrudController::deleteEntity() and send that processed entity to some custom API endpoint, but I haven't figured out yet how to override the list page where all the entities will be displayed. Is there a way to do that ? or am I missing something ?

In some other admin panels I've used in the past (for example Laravel AdminLTE) it was always possible to override the ajax request with custom data (not necessary from it's own database, rather from REST API).

edenreich commented 3 years ago

It might be sufficient to pass a custom method / callback to AbstractCrudController::index on line 127, 125, to the PaginatorFactory::create and the EntityFactory::createCollection()

and in this callback you can then fetch whatever data you'll need from any source

edenreich commented 3 years ago

In case someone else get to face this issue, until there is a proper fix you can use the following method to pass your own custom resources / dto (non database resources) to crud list view (bare in mind I left all without extra abstraction for simplicity purposes):

use App\Entity\Article;
use Doctrine\ORM\EntityManagerInterface;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext;
use EasyCorp\Bundle\EasyAdminBundle\Event\BeforeCrudActionEvent;
use EasyCorp\Bundle\EasyAdminBundle\Event\AfterCrudActionEvent;
use EasyCorp\Bundle\EasyAdminBundle\Security\Permission;
use EasyCorp\Bundle\EasyAdminBundle\Exception\ForbiddenActionException;
use EasyCorp\Bundle\EasyAdminBundle\Collection\FieldCollection;
use EasyCorp\Bundle\EasyAdminBundle\Config\KeyValueStore;
use EasyCorp\Bundle\EasyAdminBundle\Factory\FilterFactory;
use EasyCorp\Bundle\EasyAdminBundle\Factory\EntityFactory;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Collection\EntityCollection;
use EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto;

class ArticleCrudController extends AbstractCrudController
{
    private $metadata;

    public function __construct(EntityManagerInterface $entityManager)
    {
        $this->metadata = $entityManager->getClassMetadata(Article::class);
    }

     ...

    public function index(AdminContext $context)
    {
        $event = new BeforeCrudActionEvent($context);
        $this->get('event_dispatcher')->dispatch($event);
        if ($event->isPropagationStopped()) {
            return $event->getResponse();
        }

        if (!$this->isGranted(Permission::EA_EXECUTE_ACTION)) {
            throw new ForbiddenActionException($context);
        }

        $fields = FieldCollection::new($this->configureFields(Crud::PAGE_INDEX));
        $filters = $this->get(FilterFactory::class)->create($context->getCrud()->getFiltersConfig(), $fields, $context->getEntity());
        // $queryBuilder = $this->createIndexQueryBuilder($context->getSearch(), $context->getEntity(), $fields, $filters);
        // $paginator = $this->get(PaginatorFactory::class)->create($queryBuilder);

        // $entities = $this->get(EntityFactory::class)->createCollection($context->getEntity(), []);
        $dtoFromMockedResponse = [
            new Article(['title' => 'test title 1', 'content' => 'test content 1']),
            new Article(['title' => 'test title 2', 'content' => 'test content 2']),
            new Article(['title' => 'test title 3', 'content' => 'test content 3']),
        ];
        $entityDtos = [];
        foreach ($dtoFromMockedResponse as $key => $entity) {
            $newEntityDto = new EntityDto(Article::class, $this->metadata, null, $entity);
            // $newEntityId = $newEntityDto->getPrimaryKeyValueAsString();
            $entityDtos[sprintf('%s #%s', 'Article', $key+1)] = $newEntityDto;
            $entities = EntityCollection::new($entityDtos);
        }
        $this->get(EntityFactory::class)->processFieldsForAll($entities, $fields);

        $globalActions = $this->get(EntityFactory::class)->processActionsForAll($entities, $context->getCrud()->getActionsConfig());

        $responseParameters = $this->configureResponseParameters(KeyValueStore::new([
            'pageName' => Crud::PAGE_INDEX,
            'templateName' => 'crud/index',
            'entities' => $entities,
            'paginator' => [
                'numResults' => count($dtoFromMockedResponse), 
                'hasPreviousPage' => false, 
                'hasNextPage' => false,
            ],
            'global_actions' => $globalActions,
            'filters' => $filters,
            // 'batch_form' => $this->createBatchActionsForm(),
        ]));

        $event = new AfterCrudActionEvent($context, $responseParameters);
        $this->get('event_dispatcher')->dispatch($event);
        if ($event->isPropagationStopped()) {
            return $event->getResponse();
        }

        return $responseParameters;
    }

    public function updateEntity(EntityManagerInterface $entityManager, $entityInstance): void
    {
        // @todo send update request to api
    }

    public function persistEntity(EntityManagerInterface $entityManager, $entityInstance): void
    {
        // @todo send create request to api
    }

    public function deleteEntity(EntityManagerInterface $entityManager, $entityInstance): void
    {
        // @todo send delete request to api
    }
}