【问题标题】:TYPO3/Extbase How to create unique slug within create action?TYPO3/Extbase 如何在创建动作中创建独特的 slug?
【发布时间】:2020-08-07 15:22:37
【问题描述】:

我的 TCA 中有 slug 字段,通常它可以工作,当通过 Backend > List 模块添加时,即使我不会输入任何值,unique eval 也会确保 slug 是唯一的,所以当我'将创建许多具有相同名称的行 Foo TYPO3 后端将确保它将解析为独特的 slug,如 foofoo-1foo-2 等。荣誉!:

'slug'       => [
    'exclude'     => true,
    'label'       => 'Slug',
    'displayCond' => 'VERSION:IS:false',
    'config'      => [
        'type'              => 'slug',
        'generatorOptions'  => [
            'fields'         => ['name'],
            'fieldSeparator' => '/',
            'replacements'   => [
                '/' => '',
            ],
        ],
        'fallbackCharacter' => '-',
        'eval'              => 'unique',
        'default'           => '',
        'appearance'        => [
            'prefix' => \BIESIOR\Garage\UserFunctions\SlugPrefix::class . '->getPrefix'
        ],
    ],
],

但是,当在 new/create 操作(如您所见的 extension_builder 中的典型 Extbase CRUD)中从我的表单创建新对象时,例如:

public function createAction(Car $newCar)
{
    $this->addFlashMessage(
        'The object was created. Please be aware that this action is publicly accessible unless you implement an access check. See https://docs.typo3.org/typo3cms/extensions/extension_builder/User/Index.html', 
        '', 
        \TYPO3\CMS\Core\Messaging\AbstractMessage::WARNING);
    $this->carRepository->add($newCar);
    $this->redirect('list');
}

当然 slug 是音符组。

我的第一个想法是复制TCA type='slug' 的逻辑,并使用一些自己的 JS、AJAX 和 PHP 添加此功能,但这听起来像是过载和耗时。特别是我根本不希望用户关心 slug 部分。是否有任何简单的 API 用于查找给定表的唯一 slug,可以在自定义操作中使用?

注意这个问题不是关于如何用 JS 处理它,这只是概念。对于 FE 用户,我想跳过这部分,他不需要知道 slug 是什么。只是在创建一个新对象的过程中,我想获得像 foo-123 这样的唯一值。

【问题讨论】:

    标签: typo3 frontend extbase slug typo3-10.x


    【解决方案1】:

    除了 Jonas Eberles 的回答之外,这里还有另一个例子,它也尊重 slug 字段的 eval 配置(可以是 uniqueInSiteuniqueInPid 或简单的 unique)。

    use TYPO3\CMS\Core\DataHandling\Model\RecordStateFactory;
    use TYPO3\CMS\Core\DataHandling\SlugHelper;
    use TYPO3\CMS\Core\Utility\GeneralUtility;
    
    ...
    
    public function createAction(Car $newCar)
    {
        $this->carRepository->add($newCar);
        GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager::class)->persistAll();
        $record = $this->carRepository->findByUidAssoc($newCar->getUid())[0];
    
        $tableName = 'tx_garage_domain_model_car';
        $slugFieldName = 'slug';
    
    //      Get field configuration
        $fieldConfig = $GLOBALS['TCA'][$tableName]['columns'][$slugFieldName]['config'];
        $evalInfo = GeneralUtility::trimExplode(',', $fieldConfig['eval'], true);
    
    //      Initialize Slug helper
        /** @var SlugHelper $slugHelper */
        $slugHelper = GeneralUtility::makeInstance(
            SlugHelper::class,
            $tableName,
            $slugFieldName,
            $fieldConfig
        );
    
    //      Generate slug
    
        $slug = $slugHelper->generate($record, $record['pid']);
        $state = RecordStateFactory::forName($tableName)
            ->fromArray($record, $record['pid'], $record['uid']);
    
    //      Build slug depending on eval configuration
        if (in_array('uniqueInSite', $evalInfo)) {
            $slug = $slugHelper->buildSlugForUniqueInSite($slug, $state);
        } else if (in_array('uniqueInPid', $evalInfo)) {
            $slug = $slugHelper->buildSlugForUniqueInPid($slug, $state);
        } else if (in_array('unique', $evalInfo)) {
            $slug = $slugHelper->buildSlugForUniqueInTable($slug, $state);
        }
        $newCar->setSlug($slug);
        $this->carRepository->update($newCar);
    
    }
    

    在存储库中使用自定义查找器来获取关联数组而不是 $racord 参数的映射对象

    public function findByUidAssoc($uid)
    {
        $query = $this->createQuery();
        $query->matching(
            $query->equals('uid', $uid)
        );
    
        return $query->execute(true)[0];
    }
    

    请注意,在执行上述代码之前需要对记录进行持久化。

    参考资料:

    【讨论】:

    • 谢谢,我用工作代码更新了你的示例。做它的工作。在空闲时间,我将使用这种方法添加调度程序任务的用法。
    【解决方案2】:

    根据 Elias 和 Jonas 的回答,我创建了一个可以简化事情的类,尤其是当您需要处理更多模型时

    typo3conf/ext/sitepackage/Classes/Utility/SlugUtility.php

    <?php
    namespace VENDOR\Sitepackage\Utility; // <- to be replaced with your namespace
    
    use TYPO3\CMS\Core\Database\Connection;
    use TYPO3\CMS\Core\Database\ConnectionPool;
    use TYPO3\CMS\Core\DataHandling\Model\RecordStateFactory;
    use TYPO3\CMS\Core\DataHandling\SlugHelper;
    use TYPO3\CMS\Core\Utility\GeneralUtility;
    /***
     *
     * This file is part of the "Sitepackage" Extension for TYPO3 CMS.
     *
     * For the full copyright and license information, please read the
     * LICENSE.txt file that was distributed with this source code.
     *
     *  (c) 2020 Marcus Biesioroff <biesior@gmail.com>
     *  Concept by:  Elias Häußler
     *               Jonas Eberle
     *
     ***/
    class SlugUtility
    {
        /**
         * @param int    $uid UID of record saved in DB
         * @param string $tableName Name of the table to lookup for uniques
         * @param string $slugFieldName Name of the slug field
         *
         * @return string Resolved unique slug
         * @throws \TYPO3\CMS\Core\Exception\SiteNotFoundException
         */
        public static function generateUniqueSlug(int $uid, string $tableName, string $slugFieldName): string
        {
    
            /** @var Connection $connection */
            $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($tableName);
            $queryBuilder = $connection->createQueryBuilder();
    
            $record = $queryBuilder
                ->select('*')
                ->from($tableName)
                ->where('uid=:uid')
                ->setParameter(':uid', $uid)
                ->execute()
                ->fetch();
            if (!$record) return false;
    
    //      Get field configuration
            $fieldConfig = $GLOBALS['TCA'][$tableName]['columns'][$slugFieldName]['config'];
            $evalInfo = GeneralUtility::trimExplode(',', $fieldConfig['eval'], true);
    
    //      Initialize Slug helper
            /** @var SlugHelper $slugHelper */
            $slugHelper = GeneralUtility::makeInstance(
                SlugHelper::class,
                $tableName,
                $slugFieldName,
                $fieldConfig
            );
    //      Generate slug
            $slug = $slugHelper->generate($record, $record['pid']);
            $state = RecordStateFactory::forName($tableName)
                ->fromArray($record, $record['pid'], $record['uid']);
    
    //      Build slug depending on eval configuration
            if (in_array('uniqueInSite', $evalInfo)) {
                $slug = $slugHelper->buildSlugForUniqueInSite($slug, $state);
            } else if (in_array('uniqueInPid', $evalInfo)) {
                $slug = $slugHelper->buildSlugForUniqueInPid($slug, $state);
            } else if (in_array('unique', $evalInfo)) {
                $slug = $slugHelper->buildSlugForUniqueInTable($slug, $state);
            }
            return $slug;
        }
    }
    

    在任何地方使用,例如控制器。调度器任务、存储库等。记住,记录应该是之前保存的(可能是Extbase创建的,或者只是用普通SQL创建的),只需要创建uid并且是有效的TYPO3记录。

    use VENDOR\Sitepackage\Utility\SlugUtility;
    use \TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager;
    
    ...
    
    $pageSlug = SlugUtility::generateUniqueSlug(
        5,        // int     $uid            UID of record saved in DB
        'pages',  // string  $tableName      Name of the table to lookup for uniques
        'slug'    // string  $slugFieldName  Name of the slug field
    )
    
    // or
    
    $uniqueSlug = SlugUtility::generateUniqueSlug(
        123,
        'tx_garage_domain_model_car',
        'slug'
    );
    
    // or according to the original question, 
    // if you created new model object with Extbase, 
    // persist it, create unique slug with SlugUtility 
    // set the slug property to the created model object and finally update
    
    public function createAction(Car $newCar)
    {
        $this->carRepository->add($newCar);
        GeneralUtility::makeInstance(PersistenceManager::class)->persistAll();
        $uniqueSlug = SlugUtility::generateUniqueSlug(
            $newCar->getUid(),
            'tx_garage_domain_model_car',
            'slug'
        );
        if($uniqueSlug) {
            $newCar->setSlug($uniqueSlug);
            $this->carRepository->update($newCar);
        }
        $this->redirect('list');
    }
    
    // no need for second call to persistAll() 
    // as Extbase will call it at action's finalizing.
    
    // etc.
    

    【讨论】:

    • 非常感谢这个概念和课程。但如果您的记录使用限制,请注意DefaultRestrictionContainer。例如。一条记录应该在未来显示,因此有一个未来的开始时间,然后SlugUtility::generateUniqueSlug() 中的if (!$record) return false; 将匹配。因此,您的记录在发布时不会有填充的 slug 字段。
    • 感谢 Marcus,因为 TYPO3 10 在 SlugUtility 中使用 -&gt;fetchAssociative() 而不是 -&gt;fetch()
    【解决方案3】:

    您可以直接使用SlugHelper。对于该用例,API 显然不是很流畅,但它可以工作......

    $this->carRepository->add($newCar);
    
    // probably you need to persist first - I am not sure if this is really necessary
    $this->objectManager()->get(
      \TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager::class
    )->persistAll();
    
    $table = 'tx_garage_domain_model_car';
    $field = 'slug';
    
    // a stripped down record with just the necessary fields is enough
    $record = ['name' => $newCar->getName()];
    $pid = $this->settings->persistence->... 
    
    $slugHelper = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(
      \TYPO3\CMS\Core\DataHandling\SlugHelper::class,
      $table,
      $field,
      $GLOBALS['TCA'][$table]['columns'][$field]['config']
    );
    
    $newCar->slug = $slugHelper->generate($record, $pid);
    

    【讨论】:

    • 感谢您的努力,这可能在一些修复后会起作用。不幸的是,我不能接受两个答案。我将使用 @eliash 解决方案,因为它还会检查 TCA 配置,但是,我感谢您的贡献。
    • 我认为好主意是持久化对象,然后在关联数组中重新获取它的记录。显然SlugHelper::generate() 方法检查除name 之外的其他字段,即用于页面表的languageFieldis_siteroot。将来作者很有可能还会处理其他字段,因此目前它是 IMO 最安全的方式。
    • 太棒了。我从一个 v9 项目中得到它,它不是很精致。我会为你的 SlugUtility 添加书签 :)
    • 恐怕它主要被认为是用作@internal 类,所以正如你所指出的,它确实可以/应该被打磨,特别是从 10.x 开始,slugs/routes 是强制性的
    猜你喜欢
    • 2015-02-26
    • 1970-01-01
    • 1970-01-01
    • 2017-06-01
    • 2021-11-20
    • 2016-07-07
    • 1970-01-01
    • 2011-10-29
    • 1970-01-01
    相关资源
    最近更新 更多