Best practices to create custom content elements

A while ago I initiated a discussion about simplifying the process to create custom content elements and it turns out, some people are working on that in general already. Yet, it’s unknown when things are finished and be integrated into the core.
In the meantime, I would like to set up a best practice, which can lead to command line tools helping to manage (create, update, delete) custom CTypes within own extensions.

A while ago, we have set up a directory structure which allows us to easily create new custom content elements. It works a lot with conventions.

Directory structure:

  • Configuration
    ** TCA/Overrides/CType/
    ** TSconfig/PageTS/CType/
    ** TypoScript/CType/
  • Resources/Language/CType

The core currently does not handle sub directories within Overrides. In order to make that work tt_content.php needs to be overridden:

<?php
if (!defined('TYPO3_MODE')) {
    die('Access denied.');
}

(function () {
    // T3 Uses the Finder itself to find and require the TCA/Overrides-files. Yet, not nested. That's why
    // It's done here for our custom, nested directory.
    $finder = \Symfony\Component\Finder\Finder::create();
    $finder->files()->sortByName()->depth(0)->name('*.php')->in(__DIR__ . '/CType');
    foreach ($finder as $fileInfo) {
        require $fileInfo->getPathname();
    }
})(); 

The CType php file contains following config (shortened):

// General configuration
$icon = 'registered-icon';

// Configuration by Convention
$CType = str_replace('.php', '', basename(__FILE__));
$label = 'LLL:EXT:provider/Resources/Private/Language/' . $CType . '.xlf:label';

\TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule(
    $GLOBALS['TCA']['tt_content'],
    [
        'ctrl' => [
            'typeicon_classes' => [
                $CType => $icon,
            ],
        ],
    [
        'types' => [
            $CType => [
                'showitem' => '
                Some Default Definition. F.e. palette general, hidden etc.
                ',
            ],
        ],
    ]
);


\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTcaSelectItem(
    'tt_content',
    'CType',
    [
        $label,
        $CType,
        $icon
    ],
    ' 1',
    'after'
);

The CType PageTS-config definition contains a standard definition either. Same goes for TypoScript and the CType XLF file. Furthermore the CType files are included automatically.
@import 'EXT:provider/Configuration/TSconfig/PageTS/CType/*.tsconfig'
@import 'EXT:provider/Configuration/TypoScript/CType/*.typoscript'

In order to be able to place the CTypes as we want, we prefix the Page TS config files with a number. For example: 08.provider_advertisement.tsconfig


So, whenever a new CType needs to be created, we simply copy the necessary files, perform a search and replace within each CType and do the necessary adjustments.

Despite reading out the sub directory of Overriden, no new features are introduced and it can be adapted by any T3 instance. This convention also allows to create command line scripts e.g. to handle that, even modify the CType key.

What are your thoughts about that? I am looking forward to hear your opinions on that.

Overall, I like the idea of auto-loading files and naming by convention. But I think the strategy could be more generic, making it work for any table which has a “types” section and “type” field definition in the TCA control section.

File structure:

File: Configuration/TCA/tt_content/subtype.php // CType="subtype" because "CType" is defined as type field for table "tt_content".
File: Configuration/TCA/pages/98.php // doktype=98 because "doktype" is defined as type field for table "pages".
File: Configuration/TCA/my_table/foobar.php // arbitrary table, type field defined in Configuration/TCA/my_table.php

Concept:

The same approach can then be used to load “Overrides” TCA files. Basically: sub-folder named according to table name, sub-file named according to value of TCA “type” field value.

The “non-override” TCA files in such sub-folders may need to be processed the same way we currently process override files: by assigning to $GLOBALS[‘TCA’] instead of returning an array (since they do not contain a full TCA definition and may need to assign additional values in multiple places, having them return a single array may not be wise).

This structure can then be loaded by iterating over the “types” TCA instruction, so as to not load all arbitrary files, but only load those that are actually defined as valid types in the table’s TCA or TCA override.

Other sources of definitions:

  • TSconfig is already possible to load in sub-folders; I see no immediate problem in adding an automatic loading of arbitrary files - we most likely do not need to enforce a CType sub-folder which again would make the loading strategy generic instead of content-specific.
  • TypoScript is already possible to load in sub-folders, same note as above.
  • Resources/Private/Language is not necessary to enforce as this file already has to be referenced in any TCA file you create and therefore would be a decision of the developer, not the framework. If the developer prefers to combine all labels in one file (which makes a lot of sense if translation is done by shipping XLF files to translation agencies!) then this is possible if we do not introduce a restriction here.

As for pageTSconfig it may be appropriate to create a registration method in PHP API that could be called from within the type-specific TCA files, which would generate a vanilla setup; given that the TCA files would be table-agnostic, this PHP API method would only be relevant for tt_content sub-files and therefore probably shouldn’t happen universally.

Icons:

We could establish a convention for file locations of icons associated with a given TCA table and sub-type for the table:

File: Resources/Public/Icons/my_table.svg // auto-detected icon for table "my_table"
File: Resources/Public/Icons/tt_content/my_ctype.svg // auto-detected icon for records in "tt_content" with CType="my_ctype"

And so on. These files can be detected while the TCA files of an extension gets loaded (or defined manually inside the TCA like they currently are).

Evaluation:

This could be a core feature, obviously it would then be designated a new feature - but it is equally possible to deliver such a feature as a community extension along the lines of your example. In my opinion though, there is no strict need to make such a “registration automation” feature an actual core component since it would be fully possible to deliver as optional/third-party component.

Possible conflicts:

Don’t forget about https://github.com/TYPO3-Initiatives/structured-content/blob/master/Documentation/ContentBlocks/ContentBlockRegistration.md - has to be kept in mind although I personally dislike that proposal and would prefer something along the lines of what you described; which would also be compatible with the “Content Block Registration” idea.

In fact, the “Content Block Registration” idea is equally possible to implement as a third-party extension - which I would personally prefer, so as to not impose any complex constraints on TYPO3 itself. The viability can then be demonstrated through the third-party extension and if deemed completely viable, could become a core component. If done this way, both your proposal and the “Content Block Registration” proposal could co-exist (but perhaps be mutually exclusive; use one or the other but not both) - and the community could then decide which one is preferable (through simple popularity measurement). Or they can remain as third-party options regardless of popularity.

Thanks for your response and I am glad to see positive response. Making it more generic sounds great. I also like the idea of using table_name as sub directory. Yet, there would some clarifications to be done as speaking of table_name could mean one place any table related config into that sub directory.

This structure can then be loaded by iterating over the “types” TCA instruction, so as to not load all arbitrary files, but only load those that are actually defined as valid types in the table’s TCA or TCA override.

I also like that idea.

TSconfig is already possible to load in sub-folders; // TypoScript is already possible to load in sub-folders

Yes, I find this in terms of automation and command line tools helpful though and if this idea will be implemented (core oder ext) I would like seeing that in the respective documentation. On the other hand, I agree that the path for TSconfig or TypoScript should not be enforced. In order to be more compatible with various kind of TYPO3 instances it should be recommend to have a dedicated file for each CType and the path should be configurable (within command line tools f.e.). Of course it needs to be generic, so it can be done for any table.