EC-CUBE4 ページ管理のデータを自動追加

## 新しいページを追加するには

新しいページを追加するには [Controllerのカスタマイズ > 新しいルーティングの追加](https://doc4.ec-cube.net/customize_controller) で簡単にできます。
ただし、この状態のままでは共通フッター・ヘッダーが付きません。

共通フッター ・ヘッダーを付けるには、ページの管理にデータを追加する必要があるようです。

## アノテーションを利用してページ管理のレコードを自動生成するコマンド

`@Template` と `@Route` アノテーションを目印にしてデータを取得。
そのデータをページ管理にインサートします。

すでに同じデータがある場合はスキップするので、何度実行しても問題ありません。

```php
<?php

declare(strict_types=1);

namespace Customize\Command;

use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\ORM\EntityManagerInterface;
use Eccube\Entity\Layout;
use Eccube\Entity\Page;
use Eccube\Entity\PageLayout;
use Eccube\Repository\LayoutRepository;
use Eccube\Repository\PageLayoutRepository;
use Eccube\Repository\PageRepository;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\ParserFactory;
use ReflectionClass;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Routing\Annotation\Route;

use function assert;
use function dirname;
use function pathinfo;
use function sprintf;

class CreatePageCommand extends Command
{
    protected static $defaultName = 'customize:create-page';

    /** @var PageRepository */
    private $pageRepository;

    /** @var PageLayoutRepository */
    private $pageLayoutRepository;

    /** @var LayoutRepository */
    private $layoutRepository;

    /** @var EntityManagerInterface */
    private $entityManager;

    public function __construct(
        PageRepository $pageRepository,
        PageLayoutRepository $pageLayoutRepository,
        LayoutRepository $layoutRepository,
        EntityManagerInterface $entityManager
    ) {
        parent::__construct();
        $this->pageRepository = $pageRepository;
        $this->pageLayoutRepository = $pageLayoutRepository;
        $this->layoutRepository = $layoutRepository;
        $this->entityManager = $entityManager;
    }

    protected function configure(): void
    {
        $this->setDescription('Create page record into database.');
    }

    protected function execute(InputInterface $input, OutputInterface $output): ?int
    {
        $io = new SymfonyStyle($input, $output);

        $this->entityManager->beginTransaction();
        $connection = $this->entityManager->getConnection();

        $sortNo = $connection->fetchColumn('SELECT MAX(sort_no) FROM dtb_page_layout');

        $layout = $this->layoutRepository->get(Layout::DEFAULT_LAYOUT_UNDERLAYER_PAGE);

        $reader = new AnnotationReader();

        $controllerDir = dirname(__DIR__) . '/Controller';
        $finder = Finder::create()->in($controllerDir)->name('*.php');

        foreach ($finder->getIterator() as $fileInfo) {
            $code = file_get_contents($fileInfo->getPathname());
            $parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
            $stmts = $parser->parse($code);
            if ($stmts === null) {
                continue;
            }

            $namespace = null;
            foreach ($stmts as $stmt) {
                if (! $stmt instanceof Namespace_) {
                    continue;
                }

                $namespace = $stmt->name->toString();
                break;
            }

            if ($namespace === null) {
                break;
            }

            $className = $namespace . '\\' . pathinfo($fileInfo->getPathname(),  PATHINFO_FILENAME);
            $reflectionClass = new ReflectionClass($className);
            $reflectionMethods = $reflectionClass->getMethods();

            foreach ($reflectionMethods as $reflectionMethod) {
                $route = $reader->getMethodAnnotation($reflectionMethod, Route::class);
                $template = $reader->getMethodAnnotation($reflectionMethod, Template::class);
                if ($route === null || $template === null || strpos($route->getPath(), '%eccube_admin_route%') !== false) {
                    continue;
                }

                assert($route instanceof Route);
                assert($template instanceof Template);

                if ($route->getName() === null) {
                    continue;
                }

                $page = $this->pageRepository->findOneBy([
                    'url' => $route->getName()
                ]);
                if ($page !== null) {
                    continue;
                }

                $pathInfo = pathinfo($template->getTemplate());
                $page = (new Page())->setUrl($route->getName())
                                    ->setFileName(sprintf('%s/%s', $pathInfo['dirname'], $pathInfo['filename']))
                                    ->setEditType(Page::EDIT_TYPE_DEFAULT);
                $this->entityManager->persist($page);
                $this->entityManager->flush();

                $pageLayout = (new PageLayout())->setLayoutId(Layout::DEFAULT_LAYOUT_UNDERLAYER_PAGE)
                                                ->setLayout($layout)
                                                ->setPageId($page->getId())
                                                ->setPage($page)
                                                ->setSortNo(++$sortNo);
                $this->entityManager->persist($pageLayout);
                $this->entityManager->flush();

                $io->text(
                    sprintf('id: %d, class: %s, url: %s, file_name: %s', $page->getId(), $className, $route->getName(), $template->getTemplate())
                );
            }
        }

        $this->entityManager->commit();

        $io->success('success.');

        return 0;
    }
}
```

## 実行 :rocket: 

```bash
./bin/console customize:create-page
```

Docker Compose で起動している人であれあば、以下でもOK

```bash
docker-compose exec --user apache web ./bin/console customize:create-page
```