Pimcore(Symfony+Twig)開発ティップス

Pimcore Specialities and Examples

Controller Name File Name Class Name Default View Directory
Content src/Controller/ContentController.php App\Controller\ContentController /templates/content
News src/Controller/NewsController.php App\Controller\NewsController /templates/news

The Messanger Component

Messenger: Sync & Queued Message Handling

Deploying to Production

On production, there are a few important things to think about:

Use Supervisor to keep your worker(s) running

You’ll want one or more “workers” running at all times. To do that, use a process control system like Supervisor.

Don’t Let Workers Run Forever

Some services (like Doctrine’s EntityManager) will consume more memory over time. So, instead of allowing your worker to run forever, use a flag like messenger:consume --limit=10 to tell your worker to only handle 10 messages before exiting (then Supervisor will create a new process). There are also other options like --memory-limit=128M and --time-limit=3600.

Restart Workers on Deploy

Each time you deploy, you’ll need to restart all your worker processes so that they see the newly deployed code. To do this, run messenger:stop-workers on deployment. This will signal to each worker that it should finish the message it’s currently handling and should shut down gracefully. Then, Supervisor will create new worker processes. The command uses the app cache internally - so make sure this is configured to use an adapter you like.

Use the Same Cache Between Deploys

If your deploy strategy involves the creation of new target directories, you should set a value for the cache.prefix.seed configuration option in order to use the same cache namespace between deployments. Otherwise, the cache.app pool will use the value of the kernel.project_dir parameter as base for the namespace, which will lead to different namespaces each time a new deployment is made.

メインテナンス(クローンジョブ)

メッセンジャーはリソースを浪費するため、時間制限を設けること。

https://pimcore.com/docs/pimcore/current/Development_Documentation/Getting_Started/Installation.html#page_5-Maintenance-Cron-Job

# this command needs anyway executed via cron or similar task scheduler
# it fills the message queue with the necessary tasks, which are then processed by messenger:consume
*/5 * * * * /your/project/bin/console pimcore:maintenance --async

# it's recommended to run the following command using a process control system like Supervisor
# please follow the Symfony Messenger guide for a best practice production setup: 
# https://symfony.com/doc/current/messenger.html#deploying-to-production
*/5 * * * * /your/project/bin/console messenger:consume pimcore_core pimcore_maintenance --time-limit=300

サイトマップカスタムジェネレータ(Sitemap custom generator)

新規に定義したオブジェクトであるニュースやブログ記事のサイトマップの作成について

https://github.com/pimcore/pimcore/blob/10.x/doc/Development_Documentation/18_Tools_and_Features/39_Sitemaps.md#creating-a-custom-generator


注) 既に掲載した下記内容の補足記事


ニュース記事のサイトマップジェネレータ:サンプルデモ


設定ファイルの pimcore セクションにニュースとブログ記事のサイトマップジェネレータを指定したsitemaps を追加

config/config.yaml

pimcore:
.....
.....
.....
#### Sitemaps
    sitemaps:
        generators:
            app_news:
                enabled: true
                priority: 50
                generator_id: App\Sitemaps\NewsGenerator

            app_blog:
                enabled: true
                priority: 49
                generator_id: App\Sitemaps\BlogGenerator
                
            # Pimcore ships a default document tree generator which is enabled by default
            # but you can easily disable it here.
            pimcore_documents:
                enabled: true
.....
.....

config/services.yamlparameters セクションと services セクションにサイトマップジェネレータに関わるパラメータやクラスを指定

config/services.yaml

parameters:
.....
.....
    #this is necessary for CLI commands to get the base url, eg. sitemap dump
    router.request_context.host: www.example.com
    router.request_context.scheme: https
.....
.....
services:
.....
.....
# ---------------------------------------------------------
    # Processors
    # ---------------------------------------------------------
    App\Sitemaps\Processors\AbsoluteUrlProcessor: ~
    
    # ---------------------------------------------------------
    # sitemaps
    # ---------------------------------------------------------
    App\Sitemaps\NewsGenerator:
        arguments:
            $filters:
                - '@Pimcore\Sitemap\Element\Filter\PublishedFilter'
                - '@Pimcore\Sitemap\Element\Filter\PropertiesFilter'
            $processors:
                - '@App\Sitemaps\Processors\AbsoluteUrlProcessor'
                
    App\Sitemaps\BlogGenerator:
        arguments:
            $filters:
                - '@Pimcore\Sitemap\Element\Filter\PublishedFilter'
                - '@Pimcore\Sitemap\Element\Filter\PropertiesFilter'
            $processors:
                - '@App\Sitemaps\Processors\AbsoluteUrlProcessor'
.....
.....

ニュースのサイトマップジェネレータ作成

注) 'document' => Document::getByPath('/en/info/News') ドキュメントでのパス設定に注意。

src/Sitemaps/NewsGenerator.php

<?php

/**
 * Pimcore
 *
 * This source file is available under two different licenses:
 * - GNU General Public License version 3 (GPLv3)
 * - Pimcore Enterprise License (PEL)
 * Full copyright and license information is available in
 * LICENSE.md which is distributed with this source code.
 *
 *  @copyright  Copyright (c) Pimcore GmbH (http://www.pimcore.org)
 *  @license    http://www.pimcore.org/license     GPLv3 and PEL
 */

namespace App\Sitemaps;

use Pimcore\Localization\LocaleServiceInterface;
use Pimcore\Model\DataObject\News;
use Pimcore\Model\Document;
use Pimcore\Sitemap\Element\AbstractElementGenerator;
use Pimcore\Sitemap\Element\GeneratorContext;
use Presta\SitemapBundle\Service\UrlContainerInterface;
use Presta\SitemapBundle\Sitemap\Url\UrlConcrete;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

class NewsGenerator extends AbstractElementGenerator
{
    public function __construct(protected LocaleServiceInterface $localeService, array $filters = [], array $processors = [])
    {
        parent::__construct($filters, $processors);
    }

    public function populate(UrlContainerInterface $urlContainer, string $section = null)
    {
        if (null !== $section && $section !== 'news') {
            // do not add entries if section doesn't match
            return;
        }

        $section = 'news';

        $list = new News\Listing();
        $list->setOrderKey('date');
        $list->setOrder('DESC');

        // the context contains metadata for filters/processors
        // it contains at least the url container, but you can add additional data
        // with the params parameter
        $context = new GeneratorContext($urlContainer, $section, ['foo' => 'bar']);

        //change locale as per multilingual setup
        $this->localeService->setLocale('en');

        /** @var News $newsArticle */
        foreach ($list as $newsArticle) {
            // only add element if it is not filtered
            if (!$this->canBeAdded($newsArticle, $context)) {
                continue;
            }

            // use a link generator to generate an URL to the article
            // you need to make sure the link generator generates an absolute url
            $link = $newsArticle->getClass()->getLinkGenerator()->generate($newsArticle, [
                'referenceType' => UrlGeneratorInterface::ABSOLUTE_URL,
                'document' => Document::getByPath('/en/info/News')
            ]);

            // create an entry for the sitemap
            $url = new UrlConcrete($link);

            // run url through processors
            $url = $this->process($url, $newsArticle, $context);

            // processors can return null to exclude the url
            if (null === $url) {
                continue;
            }

            // add the url to the container
            $urlContainer->addUrl($url, $section);
        }
    }
}

ブログ記事のサイトマップジェネレータ作成

src/Sitemaps/BlogGenerator.php

<?php

/**
 * Pimcore
 *
 * This source file is available under two different licenses:
 * - GNU General Public License version 3 (GPLv3)
 * - Pimcore Enterprise License (PEL)
 * Full copyright and license information is available in
 * LICENSE.md which is distributed with this source code.
 *
 *  @copyright  Copyright (c) Pimcore GmbH (http://www.pimcore.org)
 *  @license    http://www.pimcore.org/license     GPLv3 and PEL
 */

namespace App\Sitemaps;

use Pimcore\Localization\LocaleServiceInterface;
use Pimcore\Model\DataObject\Blog;
use Pimcore\Model\Document;
use Pimcore\Sitemap\Element\AbstractElementGenerator;
use Pimcore\Sitemap\Element\GeneratorContext;
use Presta\SitemapBundle\Service\UrlContainerInterface;
use Presta\SitemapBundle\Sitemap\Url\UrlConcrete;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

class BlogGenerator extends AbstractElementGenerator
{
    
    public function __construct(protected LocaleServiceInterface $localeService, array $filters = [], array $processors = [])
    {
        parent::__construct($filters, $processors);
    }
                        
    public function populate(UrlContainerInterface $urlContainer, string $section = null)
    {
        if (null !== $section && $section !== 'blog') {
            // do not add entries if section doesn't match
            return;
        }

        $section = 'blog';

        $list = new Blog\Listing();
        $list->setOrderKey('date');
        $list->setOrder('DESC');

        // the context contains metadata for filters/processors
        // it contains at least the url container, but you can add additional data
        // with the params parameter
        $context = new GeneratorContext($urlContainer, $section, ['foo' => 'bar']);

        //change locale as per multilingual setup
        $this->localeService->setLocale('en');

        /** @var Blog $blogArticle */
        foreach ($list as $blogArticle) {
            // only add element if it is not filtered
            if (!$this->canBeAdded($blogArticle, $context)) {
                continue;
            }

            // use a link generator to generate an URL to the article
            // you need to make sure the link generator generates an absolute url
            $link = $blogArticle->getClass()->getLinkGenerator()->generate($blogArticle, [
                'referenceType' => UrlGeneratorInterface::ABSOLUTE_URL,
                'document' => Document::getByPath('/en/Blog')
            ]);

            // create an entry for the sitemap
            $url = new UrlConcrete($link);

            // run url through processors
            $url = $this->process($url, $blogArticle, $context);

            // processors can return null to exclude the url
            if (null === $url) {
                continue;
            }

            // add the url to the container
            $urlContainer->addUrl($url, $section);
        }
    }
}

絶対パスによるサイトマップ作成のための Processor を作成

src/Sitemaps/Processors/AbsoluteUrlProcessor.php

<?php

declare(strict_types=1);

/**
 * Pimcore
 *
 * This source file is available under two different licenses:
 * - GNU General Public License version 3 (GPLv3)
 * - Pimcore Commercial License (PCL)
 * Full copyright and license information is available in
 * LICENSE.md which is distributed with this source code.
 *
 *  @copyright  Copyright (c) Pimcore GmbH (http://www.pimcore.org)
 *  @license    http://www.pimcore.org/license     GPLv3 and PCL
 */

namespace App\Sitemaps\Processors;

use Pimcore\Model\Element\ElementInterface;
use Pimcore\Sitemap\Element\GeneratorContextInterface;
use Pimcore\Sitemap\Element\ProcessorInterface;
use Pimcore\Sitemap\UrlGeneratorInterface;
use Presta\SitemapBundle\Sitemap\Url\Url;
use Presta\SitemapBundle\Sitemap\Url\UrlConcrete;

class AbsoluteUrlProcessor implements ProcessorInterface
{
    /**
     * @var UrlGeneratorInterface
     */
    private $urlGenerator;

    public function __construct(UrlGeneratorInterface $urlGenerator)
    {
        $this->urlGenerator = $urlGenerator;
    }

    public function process(Url $url, ElementInterface $element, GeneratorContextInterface $context)
    {
        $path = $this->urlGenerator->generateUrl($url->getLoc());
        $url  = new UrlConcrete($path);

        return $url;
    }
}

キャッシュをクリアしアクセス権を修復

# cd project_folder
# bin/console cache:clear
# chown -R www-data:www-data ./

サイトマップを出力

# bin/console presta:sitemaps:dump
Dumping sitemaps section blog into project_folder/public directory
Created/Updated the following sitemap files:
    sitemap.default.xml
    sitemap.news.xml
    sitemap.blog.xml
    sitemap.xml

ニュースまたはブログの各サイトマップジェネレータで指定したセクションを指定して出力

# bin/console presta:sitemaps:dump --section=news
or
# bin/console presta:sitemaps:dump --section=blog

多言語対応の場合、以下ファイルを追加で作成

config/packages/presta_sitemap.yaml

presta_sitemap:
    alternate:
        enabled: true
        default_locale: 'en'
        locales: ['en', 'ja']
        i18n: symfony

サイトマップバンドルの設定確認

# bin/console clear:cache
# chown -R www-data:www-data

# bin/console debug:config PrestaSitemapBundle

Current configuration for "PrestaSitemapBundle"
===============================================

presta_sitemap:
    defaults:
        lastmod: null
        priority: null
        changefreq: null
    alternate:
        enabled: true
        default_locale: en
        locales:
            - en
            - ja
        i18n: symfony
    generator: presta_sitemap.generator_default
    dumper: presta_sitemap.dumper_default
    timetolive: 3600
    sitemap_file_prefix: sitemap
    items_by_set: 50000
    route_annotation_listener: true
    dump_directory: xxx/xxx/project/public
    default_section: default

layout.html.twig

<head>
.....
.....
    {% do pimcore_head_script().appendFile('https://code.jquery.com/jquery-3.6.1.slim.min.js',null,null,{ integrity:"sha384-MYL22lstpGhSa4+udJSGro5I+VfM13fdJfCbAzP9krCEoK5r2EDFdgTg2+DGXdj+",crossorigin:"anonymous"}) %}
.....
.....
</head>