Changing and Extending
If you need additional functionality or the existing functionality of the extension isn't quite what you need, this section tells you how to change the behavior of the Interest extension. It also tells you how to extend the functionality, as well as a bit about the extension's inner workings.
PSR-14 Events
Events
The events are listed in order of execution.
- class HttpRequestRouterHandleByEvent
-
- Fully qualified name
-
\Pixelant\
Interest\ Router\ Event\ Http Request Router Handle By Event
Called in
Http
. Can be used to modify the request and entry point parts before they are passed on to a RequestHandler.Request Router:: handle By Method () EventHandlers for this event should implement
\Pixelant\
.Interest\ Router\ Event\ Http Request Router Handle By Event Handler Interface - getEntryPointParts ( )
-
Returns an array of the entry point parts, i.e. the parts of the URL used to detect the correct entry point. Given the URL
http://
and the default entry pointwww. example. com/ rest/ tt_ content/ Content Remote Id rest
, the entry point parts will be['tt_
.content', 'Content Remote Id'] - returntype
-
array
- class RecordOperationSetupEvent
-
- Fully qualified name
-
\Pixelant\
Interest\ Data Handling\ Operation\ Event\ Record Operation Setup Event
Called inside the
Abstract
when aRecord Operation::__ construct () *Record
object has been initialized, but before data validations.Operation EventHandlers for this event should implement
\Pixelant\
.Interest\ Data Handling\ Operation\ Event\ Record Operation Event Handler Interface EventHandlers for this event can throw these exceptions:
\Pixelant\
Interest\ Data Handling\ Operation\ Event\ Exception\ Stop Record Operation Exception - To quietly stop the record operation. This exception is only logged as informational and the operation will be treated as successful. E.g. used when deferring an operation.
\Pixelant\
Interest\ Data Handling\ Operation\ Event\ Exception\ Before Record Operation Event Exception - Will stop the record operation and log as an error. The operation will be treated as unsuccessful.
- class RecordOperationInvocationEvent
-
- Fully qualified name
-
\Pixelant\
Interest\ Data Handling\ Operation\ Event\ Record Operation Invocation Event
Called as the last thing inside the
Abstract
method, after all data persistence and pending relations have been resolved.Record Operation::__ invoke () EventHandlers for this event should implement
\Pixelant\
.Interest\ Data Handling\ Operation\ Event\ After Record Operation Event Handler Interface
- class HttpResponseEvent
-
- Fully qualified name
-
\Pixelant\
Interest\ Middleware\ Event\ Http Response Event
Called in the middleware, just before control is handled back over to TYPO3 during an HTTP request. Allows modification of the response object.
EventHandlers for this event should implement
\Pixelant\
.Interest\ Middleware\ Event\ Http Response Event Handler Interface
How it works
Internal representation and identity
Inside the extension, a record's state and identity is maintained by two data transfer object classes:
- A record's unique identity from creation to deletion is represented by
\Pixelant\
.Interest\ Domain\ Model\ Dto\ Record Instance Identifier - A record's current state, including the data that should be written to the database is represented by
\Pixelant\
.Interest\ Domain\ Model\ Dto\ Record Representation
When creating a Record
, you must also supply a Record
:
use Pixelant\Interest\Domain\Model\Dto\RecordInstanceIdentifier;
use Pixelant\Interest\Domain\Model\Dto\RecordRepresentation;
new RecordRepresentation(
[
'title' => 'My record title',
'bodytext' => 'This is a story about ...',
],
new RecordInstanceIdentifier(
'tt_content',
'ContentElementA',
'en'
)
);
Record operations
Record operations are the core of the Interest extension. Each represents one operation requested from the outside. One record operation is not the same as one database operation. Some record operations will not be executed (if it is a duplicate of the previous operation on the same remote ID) or deferred (if the record operation requires a condition to be fulfilled before it can be executed).
The record operations are invokable, and are executed as such:
Record operation types
There are three record operations:
- Create
- Update
- Delete
All are subclasses of \Pixelant\
, and share its API. Create
and Create
are direct subclasses of Abstract
, which adds a more complex constructor.
- class AbstractConstructiveRecordOperation
-
- Fully qualified name
-
\Pixelant\
Interest\ Data Handling\ Operation\ Abstract Constructive Record Operation
- class CreateRecordOperation
-
- Fully qualified name
-
\Pixelant\
Interest\ Data Handling\ Operation\ Create Record Operation
- class UpdateRecordOperation
-
- Fully qualified name
-
\Pixelant\
Interest\ Data Handling\ Operation\ Update Record Operation
- __construct ( $recordRepresentation, $metaData)
-
- param Pixelant\Interest\Domain\Model\Dto\RecordRepresentation $recordRepresentation
- param array $metaData
- getContentRenderer ( )
-
Returns a special
Content
for this operation. The data array is populated with operation-specific information when the operation object is initialized. It is not updated if this information changes.Object Renderer $contentObjectRenderer->data = [ 'table' => $this->getTable(), 'remoteId' => $this->getRemoteId(), 'language' => $this->getLanguage()->getHreflang(), 'workspace' => null, 'metaData' => $this->getMetaData(), 'data' => $this->getDataForDataHandler(), ];
Copied!- returntype
-
TYPO3CMSFrontendContentObjectContentObjectRenderer
- dispatchMessage ( $message)
-
- param \Pixelant\Interest\DataHandling\Operation\Message\MessageInterface $message
Dispatch a message, to be picked up later, in another part of the operation's execution flow.
- returntype
-
mixed
- getDataFieldForDataHandler ( $fieldName)
-
- param string $fieldName
Get the value of a specific field in the data for DataHandler. Same as
$this->get
.Data For Data Handler () [$field Name] - returntype
-
mixed
- getDataForDataHandler ( )
-
Get the data that will be written to the DataHandler. This is a modified version of the data in
$this->get
.Record Representation ()->get Data () - returntype
-
array
- getDataHandler ( )
-
Returns the internal DataHandler object used in the operation.
- returntype
-
PixelantInterestDataHandlingDataHandler
- getHash ( )
-
Get the unique hash of this operation. The hash is generated when the operation object is initialized, and it is not changed. This hash makes it possible for the Interest extension to know whether the same operation has been run before.
- returntype
-
string
- getLanguage ( )
-
Returns the record language represented by a
\TYPO3\
object, if set.CMS\ Core\ Site\ Entity\ Site Language $this->get
Record Representation ()->get Record Instance Identifier ()->get Language () - returntype
-
TYPO3CMSCoreSiteEntitySiteLanguage|null
- getMetaData ( )
-
Returns the metadata array for the operation. This metadata is not used other than to generate the uniqueness hash for the operation. You can use it to transfer useful information, e.g. for transformations. See: Accessing metadata
- returntype
-
array
- getRemoteId ( )
-
Returns the table name. Shortcut for
$this->get
Record Representation ()->get Record Instance Identifier ()->get Remote Id With Aspects () - returntype
-
string
- getTable ( )
-
Returns the table name. Shortcut for
$this->get
Record Representation ()->get Record Instance Identifier ()->get Table () - returntype
-
string
- getStoragePid ( )
-
Gets the PID of the record as originally set during object construction, usually by the
\Pixelant\
event.Interest\ Data Handling\ Operation\ Event\ Handler\ Resolve Storage Pid - returntype
-
void
- getUid ( )
-
Returns the record UID, or zero if not yet set.
$this->get
Record Representation ()->get Record Instance Identifier ()->get Uid () - returntype
-
int
- getUidPlaceholder ( )
-
Returns a DataHandler UID placeholder. If it has not yet been set, it will be generated as a random string prefixed with "NEW".
$this->get
Record Representation ()->get Record Instance Identifier ()->get Uid Placeholder () - returntype
-
string
- hasExecuted ( )
-
Returns true if the operation has executed the DataHandler operations.
- returntype
-
bool
- isDataFieldSet ( $fieldName)
-
- param string $fieldName
Check if a field in the data array is set. Same as
isset
.($this->get Data For Data Handler () [$field Name]) - returntype
-
bool
- isSuccessful ( )
-
Returns true if the operation has executed the DataHandler operations without errors.
- returntype
-
bool
- retrieveMessage ( $message)
-
- param string $messageFqcn
Pick the last message of class
$message
from the message queue. Returns null if no messages are left in the queue.Fqcn - returntype
-
PixelantInterestDataHandlingOperationMessageMessageInterface|null
- setDataFieldForDataHandler ( $fieldName, $value)
-
- param string $fieldName
Set the value of a specific field in the data for DataHandler. Same as:
- returntype
-
void
- setDataForDataHandler ( $dataForDataHandler)
-
Set the data that will be written to the DataHandler.
- param array $dataForDataHandler
- setHash ( $hash)
-
- param string $hash
Override the record operation's uniqueness hash. Changing this value can have severe consequences for data integrity.
- returntype
-
void
- setStoragePid ( $storagePid)
-
- param int $storagePid
Sets the storage PID. This might override a PID set by the
\Pixelant\
event, which usually handles this task.Interest\ Data Handling\ Operation\ Event\ Handler\ Resolve Storage Pid - returntype
-
void
Record Operation Messages
Classes implementing \Pixelant\
can be used to carry information within the execution flow of an instance of \Pixelant\
. This is especially useful between EventHandlers.
For example, \Pixelant\
is used to carry information about pending relations between the event that discovers them and the event that persists the information to the database — if the record operation was successful.
Sending a message in \Pixelant\
:
Retrieving messages and using the message data to persist the information to the database in \Pixelant\
:
Mapping table
The extension keeps track of the mapping between remote IDs and TYPO3 records in the table tx_
. In addition to mapping information, the table contains metadata about each record.
Warning
You should never access the tx_
table directly, but use the classes and methods described here.
Touching and the touched
When a record is created or updated, the touched
timestamp is updated. The timestamp is also updated if the remote request intended to update the record, but the Interest extension decided not to do it, for example because there was nothing to change. In this way, the time a record was last touched may more recent than the record's modification date.
The time the record was last touched can help you verify that a request was processed — or to find the remote IDs that were not mentioned at all. In the latter case, knowing remote IDs that are no longer updated regularly can tell you which remote IDs should be deleted.
Relevant methods
- class RemoteIdMappingRepository
-
- Fully qualified name
-
\Pixelant\
Interest\ Domain\ Repository\ Remote Id Mapping Repository
- touch ( $remoteId)
-
Touches the remote ID and nothing else. Sets the
touched
timestamp for the remote ID to the current time.- param string $remoteId
-
The remote ID of the record to touch.
- touched ( $remoteId)
-
Returns the touched timestamp for the record.
- param string $remoteId
-
The remote ID of the record to touch.
- returntype
-
int
- findAllUntouchedSince ( $timestamp, $excludeManual = true)
-
Returns an array containing all remote IDs that have not been touched since
$timestamp
.- param int $timestamp
-
Unix timestamp.
- param bool $excludeManual
-
When true, remote IDs flagged as manual will be excluded from the result. Usually a good idea, as manual entries aren't usually a part of any update workflow.
- returntype
-
bool
- findAllTouchedSince ( $timestamp, $excludeManual = true)
-
Returns an array containing all remote IDs that have been touched since
$timestamp
.- param int $timestamp
-
Unix timestamp.
- param bool $excludeManual
-
When true, remote IDs flagged as manual will be excluded from the result. Usually a good idea, as manual entries aren't usually a part of any update workflow.
- returntype
-
bool
Example
Fetching all remote IDs that have not been touched since the same time yesterday.
use Pixelant\Interest\Domain\Model\Dto\RecordInstanceIdentifier;
use Pixelant\Interest\Domain\Model\Dto\RecordRepresentation;
use Pixelant\Interest\Domain\Repository\RemoteIdMappingRepository;
use Pixelant\Interest\DataHandling\Operation\DeleteRecordOperation;
use TYPO3\CMS\Core\Utility\GeneralUtility;
$mappingRepository = GeneralUtility::makeInstance(RemoteIdMappingRepository::class);
foreach($mappingRepository->findAllUntouchedSince(time() - 86400) as $remoteId) {
(new DeleteRecordOperation(
new RecordRepresentation(
[],
new RecordInstanceIdentifier('table', $remoteId)
);
))();
}
Metadata
The mapping table also contains a field that can contain serialized meta information about the record. Any class can add and retrieve meta information from this field.
Here's two existing use cases:
- Foreign relation sorting order by
\Pixelant\
Interest\ Data Handling\ Operation\ Event\ Handler\ Foreign Relation Sorting Event Handler - File modification info by
\Pixelant\
Interest\ Data Handling\ Operation\ Event\ Handler\ Persist File Data Event Handler
Warning
Make sure that you don't mix up the metadata in the mapping table with the metadata that is sent to operations, e.g. using the meta
property or the --
option. These are not related.
Note
The field data is encoded as JSON. Any objects must be serialized so they can be stored as a string.
Relevant methods
- class RemoteIdMappingRepository
-
- Fully qualified name
-
\Pixelant\
Interest\ Domain\ Repository\ Remote Id Mapping Repository
- getMetaData ( $remoteId)
-
Retrieves all of the metadata entries as a key-value array.
- param string $remoteId
-
The remote ID of the record to return the metadata for.
- returntype
-
array
Example
This simplified excerpt from Persist
shows how metadata stored in the record is used to avoid downloading a file if it hasn't changed. If it has changed, new metadata is set.
use GuzzleHttp\Client;
use Pixelant\Interest\Domain\Repository\RemoteIdMappingRepository;
$mappingRepository = GeneralUtility::makeInstance(RemoteIdMappingRepository::class);
$metaData = $mappingRepository->getMetaDataValue(
$remoteId,
self::class
) ?? [];
$headers = [
'If-Modified-Since' => $metaData['date'],
'If-None-Match'] => $metaData['etag'],
];
$response = GeneralUtility::makeInstance(Client::class)
->get($url, ['headers' => $headers]);
if ($response->getStatusCode() === 304) {
return null;
}
$mappingRepository->setMetaDataValue(
$remoteId,
self::class,
[
'date' => $response->getHeader('Date'),
'etag' => $response->getHeader('ETag'),
]
);
return $response->getBody()->getContents();