Custom content objects
If the built-in
HBS_* content objects do not cover a required value, you
can create your own by extending
\CPSIT\.
Implementing a custom content object
The abstract base class has one abstract method to implement:
abstract protected function resolve(
array $configuration,
ValueResolutionContext $context,
): mixed;
$configuration is the TypoScript sub-tree below your content object's key.
$context exposes the current state:
$context->renderable– the EXT:form renderable being processed$context->view– its view modelModel $context->form– the active form runtimeRuntime $context->rendering– the Fluid rendering contextContext
Return any value. Strings are returned directly to the TypoScript tree. Non-string
values (arrays, objects) are automatically stored in the
Value under
a placeholder key;
Process replaces the placeholder with the real
value after the full tree has been resolved.
Example
use CPSIT\Typo3HandlebarsForms\ContentObject;
use Symfony\Component\DependencyInjection;
#[DependencyInjection\Attribute\AutoconfigureTag(
'frontend.contentobject',
['identifier' => 'MY_CUSTOM_COBJ'],
)]
final class MyCustomContentObject extends ContentObject\AbstractHandlebarsFormsContentObject
{
protected function resolve(
array $configuration,
ContentObject\Context\ValueResolutionContext $context,
): mixed {
return $context->renderable->getProperties()[$configuration['key'] ?? ''] ?? null;
}
}
Use it in TypoScript like any built-in content object:
myValue = MY_CUSTOM_COBJ
myValue.key = someProperty
stdWrap support
std is handled automatically by the base class for every
HBS_* and custom content object. If
resolve returns a stringable
value,
std is applied directly to it. If the value is non-stringable,
std receives an empty string but the original value is set as the
current so TypoScript conditions inside
std can
still read it.
Registration
The
#
attribute is sufficient for registration when the class lives in a directory that is
autowired. No explicit YAML service definition is needed.
The identifier must be globally unique across all loaded extensions. Prefixing it
with your vendor name (e.g.
MYVENDOR_) avoids collisions.
Recursing into the TypoScript tree
$context->process re-invokes the
Process pipeline with an
arbitrary TypoScript configuration array. Its primary purpose is to let your content
object re-process the current (or a different) renderable under a different configuration
block — for example, to apply a sub-section of your own configuration, or to switch the
active renderable or view model entirely before the pipeline runs.
protected function resolve(
array $configuration,
ContentObject\Context\ValueResolutionContext $context,
): mixed {
$subConfiguration = $configuration['items.'] ?? [];
if (!is_array($subConfiguration)) {
return null;
}
return $context->process($subConfiguration);
}
To process a different renderable or view model, pass them as named arguments:
$context->process($subConfig, renderable: $otherRenderable, viewModel: $otherViewModel);
Context stack
The
Context is a shared singleton (shared: true in DI). It holds the
stack of
Value objects pushed by
Process as
it walks the TypoScript tree.
Custom content objects receive the current context as a direct argument to
resolve – injecting
Context directly is not necessary and is
considered internal API.