psb / psb-foundation
TYPO3扩展开发配置框架
Requires
- php: ^8.1
- ext-fileinfo: *
- ext-simplexml: *
- typo3/cms-core: ^12.4
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.13.0
- phpunit/phpunit: ^9
- roave/security-advisories: dev-latest
- typo3/coding-standards: ^0.5.3
- typo3/testing-framework: ^8.0.9
README
增强Extbase扩展编程
重要 升级到v2版本,请参阅CHANGELOG.md!
- 它做什么?
- 为什么你应该使用它?
- 入门指南
- TCA生成
- 注册和配置插件
- 注册和配置模块
- 注册自定义页面类型
- 自动注册TypoScript文件
- 自动注册TSconfig文件
- 自动注册图标
- 扩展设置
- 辅助类
它做什么?
此扩展
- 通过读取其php属性来为您的主域模型生成TCA
- 通过属性类构造函数属性提供TCA的自动完成
- 为您的主域模型生成数据库定义
- 简化模块、插件和页面类型的注册
- 自动注册现有的FlexForms、TypoScript和图标
- 提供易于使用的带有自动引用创建的前端文件上传
- 提供具有附加选项(例如复数形式和更好的变量语法)的优化TranslateViewHelper
- 允许定义可以在整个网站上使用的前端变量(例如IBAN或电话号码)
- 提供方便的方式来访问常用核心功能
重要
psb_foundation旨在简化常见的重复设置。尽管它提供了一套灵活的配置和工具,但你可能无法使用它来映射某些复杂的场景。但你可以继续使用此扩展。例如,你可以使用TCA的自动生成,并通过TCA/Overrides/
中的文件添加特殊设置,就像平常一样。
为什么你应该使用它?
此扩展的目标是
- 将不同的配置点汇集在一起,以改善可读性、可维护性和开发时间
- 允许对TCA设置进行代码补全
- 减少重复代码
- 减少硬编码的字符串标识符和键的数量,从而降低因拼写错误而导致的错误可能性
入门指南
创建以下文件: EXT:your_extension/Classes/Data/ExtensionInformation.php
。定义类并使其扩展 PSB\PsbFoundation\Data\AbstractExtensionInformation
。
示例
<?php declare(strict_types=1); /** copyright... */ namespace Vendor\ExtensionName\Data; use PSB\PsbFoundation\Data\AbstractExtensionInformation; class ExtensionInformation extends AbstractExtensionInformation { }
如果你想要,可以使用继承的函数,如 getExtensionKey()
或 getVendorName()
来去除硬编码的标识符。
psb_foundation(别忘了将其添加到你的扩展依赖项中)会搜索所有活动包中的文件 EXT:your_extension/Classes/Data/ExtensionInformation.php
并检查该类是否实现了 PSB\PsbFoundation\Data\ExtensionInformationInterface
。所有满足这些要求的扩展在自动配置过程中都会被考虑,例如在TCA生成或图标注册期间。
TCA生成
您不再需要在Configuration/TCA
中为您的领域模型创建特殊文件!psb_foundation将扫描您的Classes/Domain/Model
目录中的所有类(跳过抽象类),这些类在其PHPDoc注释中具有类型为PSB\PsbFoundation\Attribute\TCA\Ctrl
的属性。脚本检查您的模型是否与数据库中的现有表相关联,并检测是否扩展了来自不同扩展的其他模型,并相应地操作TCA。
您可以通过属性提供配置选项。属性PSB\PsbFoundation\Attribute\TCA\Column
提供了所有TCA类型的通用配置字段,例如必填、显示条件和非空。可以在psb_foundation/Classes/Attribute/TCA/ColumnType/
中找到具有特定类型默认值的附加属性。
简单示例
use PSB\PsbFoundation\Attribute\TCA\Column; use PSB\PsbFoundation\Attribute\TCA\ColumnType\Input; use PSB\PsbFoundation\Attribute\TCA\Ctrl; #[Ctrl(label: 'name', searchFields: ['description', 'name')] class YourClass { #[Column(required: true)] #[Input(eval: 'trim')] protected string $name = ''; // You can leave out the brackets if you are fine with the default values. #[Input] protected string $inputUsingTheDefaultValues = ''; #[Column(exclude: true)] #[Check] protected bool $adminOnly = true; ... }
没有TCA[...]属性的属性将不会在TCA生成中考虑。如果CTRL属性(默认情况下是这样的)中定义了特定字段,则将自动添加一些配置。
enableColumns字段的字段将添加到所有类型的showitems
末尾的附加标签页中。
关联类型inline
和select
有一个名为linkedModel
的特殊属性。您可以使用相关领域模型的类名而不是使用foreign_table
,psb_foundation将相应地将表名插入到TCA中。
此外,foreign_field
和mm_opposite_field
属性接受属性名称。这些将被转换为列名。
扩展示例
use PSB\PsbFoundation\Attribute\TCA\Column; use PSB\PsbFoundation\Attribute\TCA\ColumnType\Inline; use PSB\PsbFoundation\Attribute\TCA\ColumnType\Select; use PSB\PsbFoundation\Attribute\TCA\ColumnType\Text; use PSB\PsbFoundation\Attribute\TCA\Ctrl; #[Ctrl( hideTable: true, label: 'richText', labelAlt: [ 'someProperty', 'anotherProperty', ], labelAltForce: true, sortBy: 'customSortField', type: 'myType', )] class YourModel { /* * This constant is used as value for TCA-property "items". The keys will be used as label identifiers - converted * to lowerCamelCase: * EXT:your_extension/Resources/Private/Language/Backend/Configuration/TCA/(Overrides/)[modelName].xlf:myType.default * EXT:your_extension/Resources/Private/Language/Backend/Configuration/TCA/(Overrides/)[modelName].xlf:myType.recordWithImage */ public const TYPES = [ 'DEFAULT' => 'default', 'RECORD_WITH_IMAGE' => 'recordWithImage', ]; #[Select(items: self::TYPES)] protected string $myType = self::TYPES['DEFAULT']; #[Text( cols: 40, enableRichtext: true, rows: 10, )] protected string $richText = ''; /** * @var ObjectStorage<AnotherModel> */ #[Inline( linkedModel: AnotherModel::class, foreignField: 'yourModel', )] protected ObjectStorage $inlineRelation; #[Column(position: 'before:propertyName2')] #[Select(linkedModel: AnotherModel::class)] protected ?AnotherModel $simpleSelectField = null; // This field is only added to the types 'interview' and 'profile'. #[Column(typeList: 'interview, profile')] #[File( allowed: [ 'jpeg', 'jpg', ], maxItems: 1, )] protected ?FileReference $image = null; ... }
标签和调色板
使用位置参数可以将字段分配给特定的标签页或调色板。
示例
#[Column(position: 'palette:my_palette')] protected string $description = ''; #[Column(position: 'tab:my_tab')] protected string $note = '';
如果没有进一步配置,则标签页和调色板将使用给定的标识符进行注册,并添加到showitems列表的末尾。但您可以添加有关标签页和调色板的附加信息。目前,调色板和标签页的标识符必须以snake_case编写!
use PSB\PsbFoundation\Attribute\TCA\Column; use PSB\PsbFoundation\Attribute\TCA\Palette; use PSB\PsbFoundation\Attribute\TCA\Tab; /** * @TCA\Tab(identifier="my_tab", label="Custom label", position="after:someProperty") */ #[Ctrl] #[Palette( description: 'LLL:EXT:[...]', identifier: 'my_palette', position: 'before:someProperty' )] #[Tab( identifier: 'my_tab', label: 'Custom label', position: 'after:someProperty' )] class YourModel { #[Column(position: 'palette:my_palette')] protected string $description = ''; #[Column(position: 'tab:my_tab')] protected string $note = ''; ... }
也可以使用tab:
前缀来指定调色板的位置。
如果属性引用了调色板内的字段,则可以使用两个额外的特殊前缀
- newLineAfter
- newLineBefore
这些将在这些字段之间插入额外的--linebreak--
。
标签页和调色板的标签由以下方式确定
- 如果给定,请使用属性。(可以是LLL引用)
- 如果存在,请使用默认语言标签。(见默认语言标签路径和附加配置选项)
- 使用标识符。
调色板描述的行为类似
- 如果给定,请使用属性。(可以是LLL引用)
- 如果存在,请使用默认语言标签。(见默认语言标签路径和附加配置选项)
- 留空。
数据库定义
基于用于属性的字段使用的TCA属性自动添加数据库定义。您可以通过自己定义字段或使用属性PSB\PsbFoundation\Attribute\TCA\Column
的databaseDefinition
属性来覆盖自动生成的定义。
优先顺序
- ext_tables.sql
- #[Column(databaseDefinition: '...')]
- ColumnType属性默认值
扩展领域模型
当您扩展领域模型(即使是那些不使用psb_foundation的扩展)时,您必须添加@TCA\Ctrl属性!您有覆盖ctrl设置的选项。如果您不想覆盖任何内容,只需省略括号即可。在这种情况下,属性类的默认值将没有效果。
默认语言标签路径
如果您没有为这些语言标签提供自定义值,则将尝试使用这些语言标签。路径始终为EXT:your_extension/Resources/Private/Language/Backend/Configuration/TCA/(Overrides/)[modelName].xlf
。
当您为选择字段使用items属性时,您可能提供一个简单的关联数组。它将被转换为所需的多级格式。标签将这样构建
[propertyName].[arrayKeyTransformedToLowerCamelCase]
注册和配置插件
-
Classes/Data/ExtensionInformation.php
public function __construct() { parent::__construct(); $this->addPlugin(GeneralUtility::makeInstance(PluginConfiguration::class, controllers: [MyController::class], name: 'MyPlugin', )); $this->addPlugin(GeneralUtility::makeInstance(PluginConfiguration::class, controllers: [ MyController::class => [ 'specificAction', ], AnotherController::class => [ 'specificAction', // This is not necessarily the default action! 'anotherSpecificAction', ], ]], group: 'customTabInContentElementWizard' name: 'AnotherPlugin', )); }
-
Classes/Controller/YourController.php
use PSB\PsbFoundation\Attribute\PluginAction; class YourController extends ActionController { #[PluginAction] public function simpleAction(): ResponseInterface { ... } #[PluginAction(default: true)] public function mainAction(): ResponseInterface { ... } #[PluginAction(uncached: true)] public function uncachedAction(): ResponseInterface { ... } }
没有PluginAction
属性的Action不会注册 - 即使在ExtensionInformation.php
中的可选操作列表中提到也不会注册!
如果没有提供动作列表,所有具有 PluginAction
属性的动作将被注册。默认动作以及哪些动作不应该缓存仅由属性属性值决定。请检查 EXT:psb_foundation/Classes/Attribute/
中的默认值和注释。
FlexForms
如果存在一个名为 EXT:your_extension/Configuration/FlexForms/[PluginName].xml
的文件,它将被自动注册。您可以通过向 PluginConfiguration
构造函数传递 flexForm
属性的值来覆盖此默认设置。您可以通过提供位于 Configuration/FlexForms/
目录内的文件名或以 EXT:
开头的完整文件路径来提供文件名。
内容元素向导
插件将自动添加到向导中。每个供应商将有一个标签页。您可以通过设置 PluginConfiguration
的 group
属性来覆盖您的向导条目位置。如果定义了以下语言标签,将自动考虑
EXT:your_extension/Resources/Private/Language/Backend/Configuration/TsConfig/Page/Mod/Wizards/newContentElement.xlf
[group].elements.[pluginName].description
[group].elements.[pluginName].title
如果标题的标签不存在,将尝试使用此标签:EXT:your_extension/Resources/Private/Language/Backend/Configuration/TCA/Overrides/tt_content.xlf:plugin.[pluginName].title
此标签也用于选择框项(CType)。如果它也不存在,则将使用插件名称作为后备。
[group]
默认为供应商名称(小写),如果没有在 PluginConfiguration
中设置。这也定义了内容元素向导的标签页。如果创建了一个新标签页,其标签将从这里获取:EXT:your_extension/Resources/Private/Language/Backend/Configuration/TsConfig/Page/Mod/Wizards/newContentElement.xlf:[group].header
单插件的自定义页面类型
您可以添加一个自定义页面类型,该类型仅渲染特定的插件。只需在您的 PluginConfiguration 中定义一个 typeNum 即可
Classes/Data/ExtensionInformation.php
public function __construct() { parent::__construct(); $this->addPlugin(GeneralUtility::makeInstance(PluginConfiguration::class, controllers: [ MyController::class ], name: 'MyPlugin', typeNum: 1589385441, )); }
psb_foundation 将创建必要的 TypoScript,以便可以通过请求参数 type=1589385441
直接调用此插件。如果您定义了 typeNum,您可以为此页面类型添加更多特定信息
注册和配置模块
此过程与插件注册的方式非常相似。您只需添加一个特殊的注册文件(由核心要求)即可。此文件的 内容始终几乎相同(见下面的示例)——只需将命名空间适应您的 ExtensionInformation 类。查看配置类以查看所有可用选项及其默认值。
-
Classes/Data/ExtensionInformation.php
public function __construct() { parent::__construct(); $this->addMainModule(GeneralUtility::makeInstance(MainModuleConfiguration::class, key: 'my_main_module', position: ['after' => 'web'] )); $this->addModule(GeneralUtility::makeInstance(ModuleConfiguration::class, controllers: [MyController::class], key: 'my_module', parent: 'my_main_module' )); }
-
Configuration/Backend/Modules.php
<?php declare(strict_types=1); use PSB\PsbFoundation\Data\ExtensionInformation; // <-- Change this to your namespace! use PSB\PsbFoundation\Service\Configuration\ModuleService; use TYPO3\CMS\Core\Utility\GeneralUtility; return GeneralUtility::makeInstance(ModuleService::class) ->buildModuleConfiguration(GeneralUtility::makeInstance(ExtensionInformation::class));
-
Classes/Controller/YourModuleController.php
use PSB\PsbFoundation\Attribute\ModuleAction; use PSB\PsbFoundation\Controller\Backend\AbstractModuleController; class YourModuleController extends AbstractModuleController { #[ModuleAction(default: true)] public function mainAction(): ResponseInterface { ... return $this->htmlResponse(); } #[ModuleAction] public function simpleAction(): ResponseInterface { ... return $this->htmlResponse(); } }
AbstractModuleController 类包含一些基本的模板准备,允许您以与插件控制器相同的方式渲染模板:return $this->htmlResponse()
!
模块需要提供三个标签
以下后备方案考虑了主模块和子模块,如果没有指定自定义值
- 语言文件:
EXT:your_extension/Resources/Private/Language/Backend/Modules/[moduleName].xlf:
(文件名以小写开头!) - 图标标识符:
extension-key-module-your-module-name
(即您的文件名将是module-your-module-name
!)
仅针对子模块的后备方案
- 访问:
group, user
- 主模块:
web
请检查 EXT:psb_foundation/Classes/Attributes/
中的其他配置选项和注释。
注册自定义页面类型
Classes/Data/ExtensionInformation.php
public function __construct() { parent::__construct(); $this->addPageType(GeneralUtility::makeInstance(PageTypeConfiguration::class, allowedTables: ['*'], iconIdentifier: 'page-type-your-page-type-name', label: 'Your page type name', name: 'yourPageTypeName', doktype: 1691492222, )); }
键(doktype)必须是整数类型。"name" 是唯一必需的值。如果您没有提供图标标识符,将使用以下默认标识符:page-type-your-page-type-name
。标识符也用于进一步图标变体的基础。
示例('name' => 'custom'
)
- page-type-custom
- page-type-custom-contentFromPid
- page-type-custom-hideinmenu
- page-type-custom-root
您不需要提供所有这些图标。常规页面的图标将作为备用使用。您的SVG文件应位于以下目录中:EXT:your_extension/Resources/Public/Icons/
该目录中所有图标将自动按名称注册。除非定义了label
,否则将使用以下配置:EXT:your_extension/Resources/Private/Language/Backend/Configuration/TCA/Overrides/page.xlf:pageType.yourPageTypeName
。如果该键不存在,则name
将从"yourPageTypeName"转换为"Your page type name"。
自动注册TypoScript文件
如果EXT:your_extension/Configuration/TypoScript
中存在.typoscript
文件,psb_foundation将为该目录执行\PSB\PsbFoundation\Utility\TypoScript\TypoScriptUtility::registerTypoScript()
。您可以使用以下配置在模板模块中为选择项提供自定义标题:EXT:your_extension/Resources/Private/Language/Backend/Configuration/TCA/Overrides/sys_template.xlf:template.title
- 默认为'Main configuration'
。
自动注册TSconfig文件
如果此文件存在,它将被自动包含
EXT:your_extension/Configuration/User.tsconfig
(您可以使用'User'或'user')
核心已处理以下内容的包含
EXT:your_extension/Configuration/Page.tsconfig
(您可以使用'Page'或'page') https://docs.typo3.org/c/typo3/cms-core/main/en-us/Changelog/12.0/Feature-96614-AutomaticInclusionOfPageTsConfigOfExtensions.htm
自动注册图标
EXT:your_extension/Resources/Public/Icons
中所有PNG和SVG文件将自动注册。扩展密钥和文件名用作图标标识符。
示例(文件名 => 图标标识符)
logo.svg
=>your-extension-key-logo
MainMenu.png
=>your-extension-key-main-menu
扩展设置
记录缺失的语言标签
如果已激活,缺失的语言标签将存储在tx_psbfoundation_missing_language_labels
中。如果未提供自定义标签,所有缺失的默认标签(例如插件标题或字段标签)将以这种方式列出。在下次检查(每次清除缓存时)固定条目将被删除。
建议在扩展开发期间检查此表。
辅助类
ContextUtility
PSB\PsbFoundation\Utility\ContextUtility
提供了快速访问基本信息的便捷方式。
- getCurrentLocale
- isBackend
- isBootProcessRunning
- isFrontend
- isTypoScriptAvailable
GlobalVariableService
PSB\PsbFoundation\Service\GlobalVariableService
允许轻松且高效地访问常用数据。此服务是一个容器,数据提供者可以通过其类名注册。只有当它们被访问时(并且只注册一次),才会实例化提供者。可以在此GlobalVariableService中缓存不再更改的数据。
可以注册实现PSB\PsbFoundation\Service\GlobalVariableProviders\GlobalVariableProviderInterface
的自定义提供者。您可以扩展PSB\PsbFoundation\Service\GlobalVariableProviders\AbstractProvider
。此扩展内包含三个提供者。
PSB\PsbFoundation\Service\GlobalVariableProviders\RequestParameterProvider
:返回处理后的GET和POST参数。这两个数组将被合并(POST会覆盖GET),然后所有值都会先通过StringUtility::convertString()进行清理,然后转换。PSB\PsbFoundation\Service\GlobalVariableProviders\SiteConfigurationProvider
:使用SiteFinder返回SiteConfiguration。PSB\PsbFoundation\Service\GlobalVariableProviders\EarlyAccessConstantsProvider
:让您有机会定义在引导过程中非常早(在加载TypoScript之前)可以访问的常量。查看此类的注释!
示例
// get SiteConfiguration GlobalVariableService::get(SiteConfigurationProvider::class); // get all request parameters GlobalVariableService::get(RequestParameterProvider::class); // get specific request parameter GlobalVariableService::get(RequestParameterProvider::class . '.formData.hiddenInput');
StringUtility
PSB\PsbFoundation\Utility\StringUtility
包含一些字符串操作函数,例如
-
convertString:根据字符串的内容执行类型转换或其他操作,例如
- 如果无法识别其他格式,则返回字符串
-
explodeByLineBreaks:可以用于获取文件或文本字段的行作为数组
-
getNumberFormatter:返回基于当前区域的NumberFormatter
TranslateViewHelper
PSB\PsbFoundation\ViewHelpers\TranslateViewHelper
是核心TranslateViewHelper的扩展克隆。
附加功能
- 支持语言文件中的复数形式;xlf文件中的-tags可以分组定义翻译的复数形式
<group id=“day” restype=“x-gettext-plurals”> <trans-unit id=“day[0]”> <source>{0} day</source> </trans-unit> <trans-unit id=“day[1]”> <source>{0} days</source> </trans-unit> </group>
方括号中的数字定义了复数形式,定义方式请参考此处:http://docs.translatehouse.org/projects/localization-guide/en/latest/l10n/pluralforms.html 更多信息请参考\PSB\PsbFoundation\Utility\Localization\PluralFormUtility
。为了使用语言文件中定义的复数形式,您必须传递一个名为 'quantity' 的参数。
<psb:translate arguments="{quantity: 1}" id="..." />
该参数可以与其他参数组合使用(请参见以下关于命名参数的支持)。 - 命名参数:将变量传递到翻译中的更方便的方式
而不是<!-- Template file --> <f:translate arguments="{0: 'myVar', 1: 123}" id="myLabel" /> <!-- Language file --> <source>My two variables are %1$s and %2$s.</source>
您可以<!-- Template file --> <psb:translate arguments="{myVar: 'myVar', anotherVar: 123} id="myLabel" /> <!-- Language file --> <source>My two variables are {myVar} and {anotherVar}.</source>
如果未传递变量,标记将保持不变! - 新属性 "excludedLanguages"
匹配的语言键将返回 null(绕过回退!)这样,您可以在模板中不添加额外的条件包装器就从某些站点语言中删除文本。
TypoScriptProviderService
PSB\PsbFoundation\Service\TypoScriptProviderService
提供了一种方便的方法来检索特定的 TypoScript 设置。作为第一个参数,您可以提供要访问的数组路径。接下来的参数与来自 Extbase 的 ConfigurationManager 相同。
示例
// get all TypoScript $this->typoScriptProviderService->get(); // get specific part $this->typoScriptProviderService->get('config'); // get all settings from extension $this->typoScriptProviderService->get(null, ConfigurationManagerInterface::CONFIGURATION_TYPE_SETTINGS, 'extensionName', 'pluginName'); // get specific setting from extension $this->typoScriptProviderService->get('displayOptions.showPreview', ConfigurationManagerInterface::CONFIGURATION_TYPE_SETTINGS, 'extensionName', 'pluginName');
此服务返回的值已经转换为正确的类型(使用 StringUtility::convertString())。如果您将 displayOptions.showPreview = 1
设置为最后示例,它将返回一个整数。如果您将 displayOptions.showPreview = true
设置为 true,它将返回一个布尔值。未设置的常量将返回 null
而不是 {$...}
。
UploadService
此服务提供一个方法,该方法应从处理表单提交的控制器动作中调用
public function fromRequest(AbstractEntity $domainModel, Request $request): void
该方法接收一个域名模型实例,用于将上传的文件与之关联,以及一个 Extbase 请求对象。它没有返回值,但如果出现问题,将抛出异常。您必须在代码中处理这些情况!
处理上传文件的要求是表单的文件输入字段名称与相应属性的名称相匹配!
默认情况下,文件将以其客户端发送的原始名称存储在 fileadmin/user_upload/ 中(所有特殊字符都被删除)。这可以通过 TCA 进行更改
#[File( allowed: [ 'jpeg', 'jpg', ], maxItems: 1, uploadDuplicationBehaviour: DuplicationBehavior::REPLACE, uploadFileNameGeneratorPartSeparator: '_', uploadFileNameGeneratorPrefix: 'myDomainModel', uploadFileNameGeneratorProperties: ['uid'], uploadFileNameGeneratorSuffix: 'image', uploadTargetFolder: 'user_upload/my_extension', )] protected ?FileReference $image = null; #[File( allowed: [ 'pdf', ], uploadFileNameGeneratorAppendHash: true, uploadFileNameGeneratorProperties: ['category.name', 'title'], uploadFileNameGeneratorReplacements: [ ' ' => '_', ], uploadTargetFolder: '2:my_extension/my_domain_model/documents', )] protected ObjectStorage $documents;
有关某些选项的更多信息,请参阅类构造函数中的注释。