Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sonata Admin Bundle: possible to add a child admin object that can have different parents?

I'm using doctrine inheritance mapping to enable various objects to be linked to a comment entity. This is achieved through various concrete "Threads", which have a one-to-many relationship with comments. So taking a 'Story' element as an example, there would be a related 'StoryThread' entity, which can have many comments.

That is all working fine, but I'm having troubles trying to define a CommentAdmin class for the SonataAdminBundle that can be used as a child of the parent entities. For example, I'd want to be able to use routes such as:

/admin/bundle/story/story/1/comment/list /admin/bundle/media/gallery/1/comment/list

Does anyone have any pointers about how I can go about achieving this? I'd love to post some code extracts but I haven't managed to find any related documentation so don't really know the best place to start.

I've been trying to use the SonataNewsBundle as a reference because they've implemented a similar parent/child admin relationship between posts and comments, but it appears as though this relies on the 'comment' (child) admin class to be hardcoded to know that it belongs to posts, and it also seems as though it needs to have a direct many-to-one relationship with the parent object, whereas mine is through a separate "Thread" entity.

I hope this makes sense! Thanks for any help.

like image 673
RobMasters Avatar asked Dec 04 '22 03:12

RobMasters


2 Answers

Ok I managed to get this working eventually. I wasn't able to benefit from using the $parentAssociationMapping property of the CommentAdmin class, as the parent entity of a comment is a concrete instance of the Thread entity whereas the parent 'admin' class in this case is a Story (which is linked via the StoryThread). Plus this will need to remain dynamic for when I implement comments on other types of entity.

First of all, I had to configure my StoryAdmin (and any other admin classes that will have CommentAdmin as a child) to call the addChild method:

acme_story.admin.story:
      class: Acme\Bundle\StoryBundle\Admin\StoryAdmin
      tags:
        - { name: sonata.admin, manager_type: orm, group: content, label: Stories }
      arguments: [null, Acme\Bundle\StoryBundle\Entity\Story, AcmeStoryBundle:StoryAdmin]
      calls:
          - [ addChild, [ @acme_comment.admin.comment ] ]
          - [ setSecurityContext, [ @security.context ] ]

This allowed me to link to the child admin section from the story admin, in my case from a side menu, like so:

protected function configureSideMenu(MenuItemInterface $menu, $action, Admin $childAdmin = null)
{
    // ...other side menu stuff

    $menu->addChild(
        'comments',
        array('uri' => $admin->generateUrl('acme_comment.admin.comment.list', array('id' => $id)))
    );
}

Then, in my CommentAdmin class, I had to access the relevant Thread entity based on the parent object (e.g a StoryThread in this case) and set this as a filter parameter. This is essentially what is done automatically using the $parentAssociationMapping property if the parent entity is the same as the parent admin, which it most likely will be if you aren't using inheritance mapping. Here is the required code from CommentAdmin:

/**
 * @param \Sonata\AdminBundle\Datagrid\DatagridMapper $filter
 */
protected function configureDatagridFilters(DatagridMapper $filter)
{
    $filter->add('thread');
}


/**
 * @return array
 */
public function getFilterParameters()
{
    $parameters = parent::getFilterParameters();

    return array_merge($parameters, array(
        'thread' => array('value' => $this->getThread()->getId())
    ));
}

public function getNewInstance()
{
    $comment = parent::getNewInstance();

    $comment->setThread($this->getThread());
    $comment->setAuthor($this->securityContext->getToken()->getUser());

    return $comment;
}

/**
 * @return CommentableInterface
 */
protected function getParentObject()
{
    return $this->getParent()->getObject($this->getParent()->getRequest()->get('id'));
}

/**
 * @return object Thread
 */
protected function getThread()
{
    /** @var $threadRepository ThreadRepository */
    $threadRepository = $this->em->getRepository($this->getParentObject()->getThreadEntityName());

    return $threadRepository->findOneBy(array(
        $threadRepository->getObjectColumn() => $this->getParentObject()->getId()
    ));
}

/**
 * @param \Doctrine\ORM\EntityManager $em
 */
public function setEntityManager($em)
{
    $this->em = $em;
}

/**
 * @param \Symfony\Component\Security\Core\SecurityContextInterface $securityContext
 */
public function setSecurityContext(SecurityContextInterface $securityContext)
{
    $this->securityContext = $securityContext;
}
like image 99
RobMasters Avatar answered Dec 21 '22 22:12

RobMasters


An alternative to your code for direct related entities :

 public function getParentAssociationMapping()
 {
    // we grab our entity manager
    $em = $this->modelManager->getEntityManager('acme\Bundle\Entity\acme'); 

    // we get our parent object table name
    $className = $em->getClassMetadata(get_class($this->getParent()->getObject($this->getParent()->getRequest()->get('id'))))->getTableName();

    // we return our class name ( i lower it because my tables first characted uppercased )
    return strtolower( $className );
 }

be sure to have your inversedBy variable matching the $className in order to properly work

like image 36
Charles-Antoine Fournel Avatar answered Dec 21 '22 23:12

Charles-Antoine Fournel