PHP code example of maikschneider / tca-api

1. Go to this page and download the library: Download maikschneider/tca-api library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

maikschneider / tca-api example snippets




return [
    'general' => [
        'table'        => 'tx_myext_domain_model_article',
        'resourceName' => 'articles',
        'resourceType' => 'Article',
    ],
];



use MaikSchneider\TcaApi\Enum\AccessRole;

return [
    'general' => [
        'table'        => 'tx_myext_domain_model_article',
        'resourceName' => 'articles',
        'resourceType' => 'Article',
        'operations'   => ['list', 'show', 'create', 'update', 'delete'],
        'storagePid'   => 1,
    ],
    'security' => [
        'create' => AccessRole::FE_USER,
        'update' => AccessRole::FE_USER,
        'delete' => AccessRole::BE_ADMIN,
    ],
];

'columns' => [
    'title' => [
        'groups'     => ['list', 'show', 'create', 'update'],
        'lor_id' => ['groups' => ['list', 'show']],
],


// EXT:my_site/Configuration/TcaApi/Overrides/Events.php

use MaikSchneider\TcaApi\Enum\AccessRole;
use MaikSchneider\TcaApi\Filter\ExactFilter;

// Add a column
$GLOBALS['TCA_API']['events']['columns']['location'] = ['groups' => ['list', 'show']];

// Remove a column
unset($GLOBALS['TCA_API']['events']['columns']['internal_notes']);

// Add a filter
$GLOBALS['TCA_API']['events']['filters']['location'] = ExactFilter::class;

// Restrict operations to read-only
$GLOBALS['TCA_API']['events']['general']['operations'] = ['list', 'show'];

// Change a security role
$GLOBALS['TCA_API']['events']['security']['delete'] = AccessRole::FE_USER;

'columns' => [
    'title'  => ['groups' => ['list', 'show', 'create', 'update']],  // everywhere
    'teaser' => ['groups' => ['list']],                              // list only
    'body'   => ['groups' => ['show']],                              // detail view only
    'secret' => ['groups' => []],                                    // never exposed
],

'columns' => [
    'title'    => ['groups' => ['list', 'show']],
    'color_id' => ['groups' => ['list', 'show'], 'embed' => true],
    'label'    => [
        'groups'   => ['list', 'show'],
        'callback' => [ArticleCallbacks::class, 'buildLabel'],
    ],
],

final class ArticleCallbacks
{
    public function buildLabel(array $serializedRow, array $rawRow): string
    {
        // $serializedRow already contains the resolved 'color_id' relation.
        $color = $serializedRow['color_id']['name'] ?? 'n/a';

        return sprintf('%s (%s)', $serializedRow['title'] ?? '', $color);
    }
}

use MaikSchneider\TcaApi\Filter\ExactFilter;
use MaikSchneider\TcaApi\Filter\MmFilter;
use MaikSchneider\TcaApi\Filter\PartialFilter;
use MaikSchneider\TcaApi\Filter\RangeFilter;
use MaikSchneider\TcaApi\Filter\SearchFilter;
use MaikSchneider\TcaApi\Filter\WordStartFilter;

'filters' => [
    'title'  => ExactFilter::class,            // ?title=Foo  (or legacy ?filters[title]=Foo)
    'name'   => PartialFilter::class,          // ?name=oo  → LIKE %oo%
    'slug'   => WordStartFilter::class,        // ?slug=Fo  → LIKE Fo%
    'year'   => RangeFilter::class,            // ?filters[year][gte]=2020&filters[year][lte]=2024
    'q'      => [                              // Full-text search — options form
        SearchFilter::class,
        [
            'columns' => ['title', 'teaser', 'body'],
            'match'   => 'partial',            // 'partial' (default) or 'word_start'
        ],
    ],
    // Shorthand: derive MM config from TCA automatically
    'categories' => MmFilter::class,

    // Options form: supply MM table config explicitly
    'tags' => [
        MmFilter::class,
        [
            'mm_table'       => 'tx_myext_article_tag_mm',
            'mm_local_key'   => 'uid_local',
            'mm_foreign_key' => 'uid_foreign',
        ],
    ],
],

'filters' => [
    // Overrideable default — applied when ?color_id is absent
    'color_id' => [ExactFilter::class, ['default' => '1']],

    // Private filter — default always applies, cannot be overridden via URL,
    // and does not appear in the OpenAPI spec
    'deleted' => [ExactFilter::class, ['default' => '0', 'private' => true]],
],

'order' => [
    'allowed' => ['title', 'uid'],       // Columns allowed in ?order[column]=asc|desc
    'default' => ['uid' => 'asc'],       // Fallback when no order is requested
],

use MaikSchneider\TcaApi\Enum\AccessRole;

'security' => [
    // list and show are PUBLIC by default — only specify to restrict them
    'create' => AccessRole::FE_USER,    // Requires a logged-in frontend user
    'update' => AccessRole::OWNER,      // Only the record owner may update
    'delete' => AccessRole::OWNER,
],

'update' => [MyAccessChecker::class, 'checkUpdatePermission'],
// Callable receives (ServerRequestInterface $request, array $existingRecord): bool

use MaikSchneider\TcaApi\Enum\AccessRole;

'security' => [
    'create' => AccessRole::FE_USER,
    'update' => AccessRole::OWNER,
    'delete' => AccessRole::OWNER,
],
'ownership' => [
    'column' => 'fe_user_id',   // DB column holding the owner's FE user UID
],

'ownership' => [
    'column'      => 'fe_user_id',      // column checked on update/delete (also written on create)
    'setOnCreate' => 'fe_creator_id',   // additional column written on create only
],

'ownership' => [
    'column'        => 'fe_user_id',
    'beAdminBypass' => false,           // default: true
],

'cache' => [
    'enabled'            => true,
    'lifetime'           => 3600,        // TTL in seconds (default: 86400 — 24 h)
    'parametersToIgnore' => ['preview'], // bypass cache when ?preview=… is present
],

$GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['tca_api'] = [
    'backend'  => \TYPO3\CMS\Core\Cache\Backend\FileBackend::class,
    'frontend' => \TYPO3\CMS\Core\Cache\Frontend\VariableFrontend::class,
    'options'  => ['defaultLifetime' => 86400],
];

return [
    'general' => [
        'table'        => 'tx_myext_domain_model_article',
        'resourceName' => 'articles',
        'resourceType' => 'Article',
        'operations'   => ['list', 'show', 'create', 'update', 'delete'],
        'writeMode'    => 'acting_user',   // default — tracks real user
    ],
];

'general' => [
    'table'        => 'tx_myext_domain_model_internal',
    'resourceName' => 'internal-records',
    'resourceType' => 'InternalRecord',
    'operations'   => ['list', 'create'],
    'writeMode'    => 'system_admin',      // explicit opt-in
],

'columns' => [
    'profile_photo' => [
        'groups' => ['list', 'show', 'create', 'update'],
        'upload' => [
            'folder'      => '1:/user_upload/',    // cancel
            'filenameMask' => '{timestamp}_{unique}{ext}',  // optional — see below
        ],
    ],
],

'columns' => [
    'color_id'   => ['groups' => ['list', 'show'], 'embed' => true],  // explicit mode
    'categories' => ['groups' => ['list', 'show'], 'embed' => true],
],

'columns' => [
    'color_id' => ['embed' => true],  // default mode: all columns exposed, color embedded
],

'virtualProperties' => [
    'displayName' => [
        'callback' => [DisplayNameCallable::class, 'build'],
        'groups'   => ['list', 'show'],
    ],
],

'virtualProperties' => [
    'titleUppercase' => [
        'processor' => UppercaseProcessor::class,
        'groups'    => ['list', 'show'],
    ],
],

'virtualProperties' => [
    'titleCopy' => [
        'column'    => 'title',
        'processor' => MyProcessor::class,
        'groups'    => ['list', 'show'],
    ],
],

'virtualProperties' => [
    'profile_photo_thumb' => [
        'column'  => 'profile_photo',     // existing type=file column
        'image'   => [
            'maxWidth'    => 200,
            'maxHeight'   => 200,
            'cropVariant' => 'default',   // single variant → flat publicUrl/width/height
        ],
        'groups'  => ['list'],            // small thumb in list only
    ],
    'profile_photo_large' => [
        'column'  => 'profile_photo',
        'image'   => [
            'maxWidth'  => 1600,
            'maxHeight' => 1200,
            // no cropVariant → all variants returned in cropVariants map
        ],
        'groups'  => ['show'],            // full size in show only
    ],
],

'virtualProperties' => [
    'displayName' => [
        'callback' => [DisplayNameCallable::class, 'build'],
        'groups'   => ['list', 'show'],  // t
    ],
],

ApiRegistry::register('me', [
    'general' => [
        'type'         => 'userinfo',
        'table'        => 'fe_users',
        'resourceName' => 'me',
        'resourceType' => 'FeUser',
    ],
    'columns' => [
        'username'   => ['groups' => ['show']],
        'email'      => ['groups' => ['show']],
        'name'       => ['groups' => ['show']],
        'first_name' => ['groups' => ['show']],
        'last_name'  => ['groups' => ['show']],
    ],
]);



use MaikSchneider\TcaApi\OperationHandler\OperationHandlerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

interface OperationHandlerInterface
{
    public function supports(ServerRequestInterface $request, string $operation, array $config): bool;
    public function handle(ServerRequestInterface $request, array $config): ResponseInterface;
    public function getPriority(): int;
}



use MaikSchneider\TcaApi\OperationHandler\OperationHandlerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;

#[Autoconfigure(public: true)]
class PublishHandler implements OperationHandlerInterface
{
    public function supports(ServerRequestInterface $request, string $operation, array $config): bool
    {
        return $operation === 'publish'
            && ($config['general']['table'] ?? '') === 'tx_myext_domain_model_article';
    }

    public function handle(ServerRequestInterface $request, array $config): ResponseInterface
    {
        $uid = (int)$request->getAttribute('tca_api.uid');
        // … publish logic …
    }

    public function getPriority(): int
    {
        return 10;
    }
}

use MaikSchneider\TcaApi\Registry\HandlerRegistry;
use My\Extension\OperationHandler\PublishHandler;

// New operation type — priority 10 (default)
HandlerRegistry::register(PublishHandler::class);

// Override a built-in handler — checked before the built-in (priority 20 > 10)
HandlerRegistry::register(MyCustomShowHandler::class, priority: 20);



use MaikSchneider\TcaApi\Filter\FilterContext;
use MaikSchneider\TcaApi\Filter\FilterInterface;
use TYPO3\CMS\Core\Database\Query\QueryBuilder;

final class PublishedAfterFilter implements FilterInterface
{
    public function apply(QueryBuilder $qb, FilterContext $context): void
    {
        $qb->andWhere($qb->expr()->gte(
            $context->column,
            $qb->createNamedParameter((int)$context->value),
        ));
    }
}

use My\Extension\Filter\PublishedAfterFilter;

'filters' => [
    'publish_date' => PublishedAfterFilter::class,
],

'filters' => [
    'publish_date' => [
        PublishedAfterFilter::class,
        ['threshold' => 30],   // available via $context->option('threshold')
    ],
],