sonata-project / SonataAdminBundle

The missing Symfony Admin Generator
https://docs.sonata-project.org/projects/SonataAdminBundle
MIT License
2.11k stars 1.26k forks source link

Possible to copy entities #6425

Closed willemverspyck closed 4 years ago

willemverspyck commented 4 years ago

Feature Request

For some entities I want to make it possible to copy an entity using the __clone() method of PHP without saving them first. So the form is prefilled and you have to click the "Create" button before persisting it to the database. I can create an "cloneAction" in the CRUDController which is almost a copy of "createAction", but "createAction" has some method that are private and those can not be used in my own CRUDController.

I already tried the "preCreate", but that doesn't work because it isn't a reference:

/**
 * @param Request $request
 * @param mixed   $object
 */
public function preCreate(Request $request, $object): void
{
    if ($this->admin->isCurrentRoute('clone', null) && $object instanceof CloneInterface) {
        $request = $this->getRequest();
        $id = $request->get($this->admin->getIdParameter());
        $clone = $this->admin->getObject($id);

        if (!$clone) {
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
        }

        $object = clone $clone;
    }
}

If you change the $object parameters to &$object it works, but adding &$object as PR will be an BC break :-(

I can me an PR with a "cloneAction" if this is interesting for Sonata, or create a new version of "preCreate" with a reference $object with another name (and maybe returning the new object) or create a new method "getNewInstance" in CRUDController that I can override in my own CRUDController:

protected function getNewInstance()
{
        return $this->admin->getNewInstance();
}

What will you advice?

VincentLanglet commented 4 years ago

What about working in the

AdminInterface::getNewInstance();

Method directly ?

Code is

public function getNewInstance()
{
    $object = $this->getModelManager()->getModelInstance($this->getClass());

    $this->appendParentObject($object);

    foreach ($this->getExtensions() as $extension) {
        $extension->alterNewInstance($this, $object);
    }

    return $object;
}

You could say that if the request is /create?from=33 it will clone the object with the id 33 instead.

willemverspyck commented 4 years ago

Thanks @VincentLanglet

Ok, will override the "getNewInstance" for this. Is a copy method without persisting maybe interesting as PR for Sonata Admin Bundle?

VincentLanglet commented 4 years ago

It's always nice to add feature so a copyAction could theoricaly be interesting.

If we look at the RouteBuilder,

public function build(AdminInterface $admin, RouteCollection $collection)
    {
        $collection->add('list');
        $collection->add('create');
        $collection->add('batch');
        $collection->add('edit', sprintf('%s/edit', $admin->getRouterIdParameter()));
        $collection->add('delete', sprintf('%s/delete', $admin->getRouterIdParameter()));
        $collection->add('show', sprintf('%s/show', $admin->getRouterIdParameter()));
        $collection->add('export');

        if ($this->manager->hasReader($admin->getClass())) {
            $collection->add('history', sprintf('%s/history', $admin->getRouterIdParameter()));
            $collection->add('history_view_revision', sprintf('%s/history/{revision}/view', $admin->getRouterIdParameter()));
            $collection->add('history_compare_revisions', sprintf('%s/history/{base_revision}/{compare_revision}/compare', $admin->getRouterIdParameter()));
        }

        if ($admin->isAclEnabled()) {
            $collection->add('acl', sprintf('%s/acl', $admin->getRouterIdParameter()));
        }

        // add children urls
        foreach ($admin->getChildren() as $children) {
            $collection->addCollection($children->getRoutes());
        }
    }

We'll need to add $collection->add('clone'); (and a clone button, and so on).

But I don't know if we can consider as BC the fact to add such a huge feature...

willemverspyck commented 4 years ago

I added the "cloneAction" (https://github.com/willemverspyck/SonataAdminBundle/commit/8dd1b6a04f2a7b8fa14e94b35ae9f7259292c771), but wanted to check if this is compliant with the code style of Sonata, before I create tests for this and add some documentation :-)

My idea was:

    /**
     *
     */
    public function __clone()
    {
        unset($this->id);

        $this->setName(sprintf('%s (Copy)', $this->getName()));
    }

I don't like the code "$this->getRequest()->query->set('subclass', $classNames[$className]);", but on many classes or templates $this->getRequest()->query->get('subclass') is used. I need to refactor to much to make this work with discriminators / subclasses.

VincentLanglet commented 4 years ago

@sonata-project/contributors What do you think about the clone action ? Should with add this action or should we close this ?