Создание отсортированного массива для KnpLabs/KnpMenu

Я хочу создать отсортированное меню в PHP, Symfony, которое может быть очень глубоким. Для этого я добавил 2 поля в категорию db (parent_id, sort).

Моя проблема состоит в том, чтобы получить отсортированный массив, например:

 array(
    //MAIN CATEGORY 1
    array(
        'id' => 1,
        'name' => 'Main',
        'child'=> false
    ),
    //MAIN CATEGORY 2
    array(
        'id' => 2,
        'name' => 'Main2',
        'child'=> false
    ),
    //MAIN CATEGORY 3
    array(
        'id' => 6,
        'name' => 'Main3',
        'child'=> array(
            array(
                'id' => 4,
                'name' => 'Sub of Main3',
                'child'=> array(
                            'id' => 4,
                            'name' => 'Sub Sub og Main3',
                            'child'=> false
                )
            ),
            array(
                'id' => 7,
                'name' => '2. Sub og Main3',
                'child'=> false
            )
        )
    )
);

Поэтому я могу использовать его для создания меню с помощью KnpMenu Bundle. Другого способа, экономичного по производительности и работающего с этой связкой, я не нашел.

Может ли кто-нибудь помочь мне, как создать массив из БД?

Я кое-что протестировал и нашел решение с knpMenuBundle следующим образом:

namespace AppBundle\Menu;

use Knp\Menu\FactoryInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;

class Builder implements ContainerAwareInterface
{

    use ContainerAwareTrait;

    public function mainMenu(FactoryInterface $factory, array $options)
    {
        $em         =   $this->container->get('doctrine')->getManager();
        $mandant    =   $this->container->get('session')->get('mandantId');
        $nodes      =   $em->getRepository('AppBundle:Categories')->findSorted($mandant);

        $menu = $factory->createItem('root');
        $menu->addChild('Startseite', array('route' => 'homepage'));

        foreach($nodes as $node)
        {
            $childMenu = $menu->addChild($node['name'],array('route' => $node['route']));
            $this->loadChild($childMenu,$node);
        }

        return $menu;
    }

    private function loadChild($childMenu,array $node)
    {
        if(isset($node['child']))
        {
            foreach ($node['child'] as $child)
            {
                $childMenu = $childMenu->addChild($child['name'],array('route' => $child['route']));
                $this->loadChild($childMenu,$child);
            }
        }
        return;
    }

person megadruck    schedule 19.10.2017    source источник


Ответы (1)


У меня была очень похожая проблема, и вот как я ее решил.

Итак, сущность моей страницы выглядит примерно так:

<?php

namespace AppBundle\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Knp\Menu\NodeInterface;

/**
 * Page
 *
 * @ORM\Table(name="PAGE")
 * @ORM\Entity()
 */
class Page implements NodeInterface
{
    /**
     * @var int
     *
     * @ORM\Column(name="ID", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="TITLE", type="string", length=255)
     */
    private $title;

    /**
     * page | link | pdf
     * @var string
     *
     * @ORM\Column(name="CONTENT_TYPE", type="string", length=255)
     */
    private $contentType;

    /**
     * A page can have one parent
     *
     * @var FacetPage
     *
     * @ORM\ManyToOne(targetEntity="Page", inversedBy="childrenPages")
     * @ORM\JoinColumn(name="PARENT_PAGE_ID", referencedColumnName="ID")
     */
    private $parentPage;


    /**
     * A parent can have multiple children
     *
     * @var arrayCollection
     *
     * @ORM\OneToMany(targetEntity="Page", mappedBy="parentPage")
     *
     */
    private $childrenPages;

    /**
     * @var resource
     *
     * @ORM\Column(name="CONTENT", type="text", length=200000)
     */
    private $content;

    /**
     * Many pages could have many allowed roles
     *
     * @var arrayCollection
     *
     * @ORM\ManyToMany(targetEntity="Role")
     * @ORM\JoinTable(name="PAGE_ALLOWED_ROLES",
     *      joinColumns={@ORM\JoinColumn(name="page_id", referencedColumnName="ID")},
     *      inverseJoinColumns={@ORM\JoinColumn(name="role_id", referencedColumnName="ID")}
     *      )
     */
    private $allowedRoles;

    /**
     * @var string
     *
     * @ORM\Column(name="SLUG", type="string", nullable=true)
     */
    private $slug;

    /**
     * @var string
     *
     * @ORM\Column(name="PERMALINK", type="string", nullable=true)
     */
    private $permalink;

    /**
     * @var User
     *
     * @ORM\ManyToOne(targetEntity="User")
     * @ORM\JoinColumns({
     *   @ORM\JoinColumn(name="CREATED_BY", referencedColumnName="ID", nullable=false)
     * })
     *
     */
    private $author;

    /**
     * @var \DateTime
     *
     * @ORM\Column(name="CREATED_ON", type="datetime", nullable=false)
     */
    private $createdOn;

    /**
     * The default status of new pages is published
     *
     * @var string
     *
     * @ORM\Column(name="STATUS", type="string", nullable=false, )
     */
    private $status = 'published';

    /**
     * Page constructor.
     */
    public function __construct(  ) {
        //https://knpuniversity.com/screencast/collections/many-to-many-setup#doctrine-arraycollection
        $this->allowedRoles = new ArrayCollection();
        $this->childrenPages = new ArrayCollection();

    }

    /**
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * @param int $id
     */
    public function setId( int $id )
    {
        $this->id = $id;
    }

    /**
     * @return string
     */
    public function getTitle()
    {
        return $this->title;
    }

    /**
     * @param string $title
     */
    public function setTitle( string $title )
    {
        $this->title = $title;
    }

    /**
     * @return string
     */
    public function getContentType()
    {
        return $this->contentType;
    }

    /**
     * @param string $contentType
     */
    public function setContentType( string $contentType )
    {
        $this->contentType = $contentType;
    }

    /**
     * @return Page
     */
    public function getParentPage()
    {
        return $this->parentPage;
    }

    /**
     * @param Page $parentPage
     */
    public function setParentPage( Page $parentPage )
    {
        $this->parentPage = $parentPage;
    }

    /**
     * @return string
     */
    public function getContent()
    {
        return $this->content;
    }

    /**
     * @param string $content
     */
    public function setContent( string $content )
    {
        $this->content = $content;
    }

    /**
     * @return ArrayCollection|Role[]
     */
    public function getAllowedRoles()
    {
        return $this->allowedRoles;
    }

    /**
     * @param arrayCollection $allowedRoles
     */
    public function setAllowedRoles( $allowedRoles )
    {
        $this->allowedRoles = $allowedRoles;
    }

    /**
     * @return string
     */
    public function getSlug()
    {
        return $this->slug;
    }

    /**
     * @param string $slug
     */
    public function setSlug( string $slug )
    {
        $this->slug = $slug;
    }

    /**
     * @return string
     */
    public function getPermalink()
    {
        return $this->permalink;
    }

    /**
     * @param string $permalink
     */
    public function setPermalink( string $permalink )
    {
        $this->permalink = $permalink;
    }

    /**
     * @return User
     */
    public function getAuthor()
    {
        return $this->author;
    }

    /**
     * @param FacetUser $author
     */
    public function setAuthor( User $author )
    {
        $this->author = $author;
    }

    /**
     * @return \DateTime
     */
    public function getCreatedOn()
    {
        return $this->createdOn;
    }

    /**
     * @param \DateTime $createdOn
     */
    public function setCreatedOn( \DateTime $createdOn )
    {
        $this->createdOn = $createdOn;
    }

    /**
     * @return string
     */
    public function getStatus()
    {
        return $this->status;
    }

    /**
     * @param string $status
     */
    public function setStatus( string $status )
    {
        $this->status = $status;
    }

    /**
     * @return ArrayCollection
     */
    public function getChildrenPages()
    {
        return $this->childrenPages;
    }

    /**
     * @param ArrayCollection $childrenPages
     */
    public function setChildrenPages( $childrenPages )
    {
        $this->childrenPages = $childrenPages;
    }

    /**
     * Get the name of the node
     *
     * Each child of a node must have a unique name
     *
     * @return string
     */
    public function getName() {
        return $this->title;
    }

    /**
     * Get the options for the factory to create the item for this node
     *
     * @return array
     * @throws \Exception
     */
    public function getOptions() {
        if($this->contentType == 'page'){
            return [
                'route' => 'core_page_id',
                'routeParameters' => ['id'=>$this->id]
            ];
        }

        if($this->contentType == 'doc'){
            return [
                'uri'=>'/'.$this->getContent()
            ];
        }

        if($this->contentType == 'link'){
            return [
                'uri'=>$this->content
            ];
        }

        throw new \Exception('No valid options found for page type',500);
    }

    /**
     * Get the child nodes implementing NodeInterface
     *
     * @return \Traversable
     */
    public function getChildren() {
        return $this->getChildren();
    }
}

Основное отличие состоит в том, что он реализует интерфейс NodeInterface от Knp и его функции, определенные в конце сущности, getName(), getOptions() и getChildren().

Теперь перейдем к моему Builder, который в основном делает то же самое, что и ваша рекурсивная функция.

<?php

namespace AppBundle\Menu;

use AppBundle\Entity\Page;
use Knp\Menu\FactoryInterface;
use Knp\Menu\ItemInterface;
use Knp\Menu\MenuItem;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;

class Builder implements ContainerAwareInterface
{

    use ContainerAwareTrait;

    /** @var ItemInterface */
    private $menu;

    /**
     * @param FactoryInterface $factory
     * @param array $options
     *
     * @return ItemInterface
     */
    public function mainMenu(FactoryInterface $factory, array $options)
    {
        $this->menu = $factory->createItem('root');

        $this->menu->addChild('Home', array('route' => 'core_homepage'));

        $em = $this->container->get('doctrine')->getManager();

        // get all published pages
        $pages = $em->getRepository(Page::class)->findBy(['status'=>'published']);

        // build pages
        try {
            $this->buildPageTree( $pages );
        } catch ( \Exception $e ) {
            error_log($e->getMessage());
        }

        return $this->menu;
    }

    /**
     *
     * @param array $pages
     * @param Page $parent
     * @param MenuItem $menuItem
     *
     * @throws \Exception
     */
    private function buildPageTree(array $pages, $parent = null, $menuItem = null)
    {
        /** @var Page $page */
        foreach ($pages as $page) {

            // If page doesn't have a parent, and no menuItem was passed then this is a top level add.
            if(empty($page->getParentPage()) && empty($menuItem) )
                $parentMenu = $this->menu->addChild($page->getTitle(), $page->getOptions());

            // if the current page's parent is === supplied parent, go deeper
            if ($page->getParentPage() === $parent) {

                // if a menuItem was given, then this page is a child so added it to the provided menu.
                if(!empty($menuItem))
                    $parentMenu = $menuItem->addChild($page->getTitle(), $page->getOptions());

                // go deeper
                $this->buildPageTree($pages, $page, $parentMenu);
            }
        }
    }

}

Я надеюсь, что это поможет в некотором роде!

person Chris    schedule 10.04.2018