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 Pixelant\Interest\Router\Event\HttpRequestRouterHandleByEvent¶
Called in
HttpRequestRouter::handleByMethod()
. Can be used to modify the request and entry point parts before they are passed on to a RequestHandler.EventHandlers for this event should implement
Pixelant\Interest\Router\Event\HttpRequestRouterHandleByEventHandlerInterface
.- 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://www.example.com/rest/tt_content/ContentRemoteId
and the default entry pointrest
, the entry point parts will be['tt_content', 'ContentRemoteId']
.- Return type
array
- setEntryPointParts($entryPointParts)¶
- Parameters
$entryPointParts (
array
) --
- getRequest()¶
- Return type
PsrHttpMessageServerRequestInterface
- setRequest($request)¶
- Parameters
$request (
PsrHttpMessageServerRequestInterface
) --
- class Pixelant\Interest\DataHandling\Operation\Event\RecordOperationSetupEvent¶
Called inside the
AbstractRecordOperation::__construct()
when a*RecordOperation
object has been initialized, but before data validations.EventHandlers for this event should implement
Pixelant\Interest\DataHandling\Operation\Event\RecordOperationEventHandlerInterface
.EventHandlers for this event can throw these exceptions:
Pixelant\Interest\DataHandling\Operation\Event\Exception\StopRecordOperationException
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\DataHandling\Operation\Event\Exception\BeforeRecordOperationEventException
Will stop the record operation and log as an error. The operation will be treated as unsuccessful.
- getRecordOperation()¶
- Return type
PixelantInterestDataHandlingOperationAbstractRecordOperation
- class Pixelant\Interest\DataHandling\Operation\Event\RecordOperationInvocationEvent¶
Called as the last thing inside the
AbstractRecordOperation::__invoke()
method, after all data persistence and pending relations have been resolved.EventHandlers for this event should implement
Pixelant\Interest\DataHandling\Operation\Event\AfterRecordOperationEventHandlerInterface
.- getRecordOperation()¶
- Return type
PixelantInterestDataHandlingOperationAbstractRecordOperation
- class Pixelant\Interest\Middleware\Event\HttpResponseEvent¶
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\HttpResponseEventHandlerInterface
.- getResponse()¶
- Return type
PsrHttpMessageResponseInterface
- setResponse($response)¶
- Parameters
$response (
PsrHttpMessageResponseInterface
) --
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\RecordInstanceIdentifier
.A record's current state, including the data that should be written to the database is represented by
Pixelant\Interest\Domain\Model\Dto\RecordRepresentation
.
When creating a RecordRepresentation
, you must also supply a RecordInstanceIdentifier
:
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\Interest\DataHandling\Operation\AbstractRecordOperation
, and share its API. CreateRecordOperation
and CreateRecordOperation
are direct subclasses of AbstractConstructiveRecordOperation
, which adds a more complex constructor.
- class Pixelant\Interest\DataHandling\Operation\AbstractConstructiveRecordOperation¶
- class Pixelant\Interest\DataHandling\Operation\CreateRecordOperation¶
- class Pixelant\Interest\DataHandling\Operation\UpdateRecordOperation¶
- __construct($recordRepresentation, $metaData)¶
- Parameters
$recordRepresentation (
PixelantInterestDomainModelDtoRecordRepresentation
) --$metaData (
array
) --
- getContentRenderer()¶
Returns a special
ContentObjectRenderer
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.$contentObjectRenderer->data = [ 'table' => $this->getTable(), 'remoteId' => $this->getRemoteId(), 'language' => $this->getLanguage()->getHreflang(), 'workspace' => null, 'metaData' => $this->getMetaData(), 'data' => $this->getDataForDataHandler(), ];
- Return type
TYPO3CMSFrontendContentObjectContentObjectRenderer
- dispatchMessage($message)¶
- Parameters
$message (
PixelantInterestDataHandlingOperationMessageMessageInterface
) --
Dispatch a message, to be picked up later, in another part of the operation's execution flow.
- Return type
mixed
- getDataFieldForDataHandler($fieldName)¶
- Parameters
$fieldName (
string
) --
Get the value of a specific field in the data for DataHandler. Same as
$this->getDataForDataHandler()[$fieldName]
.- Return type
mixed
- getDataForDataHandler()¶
Get the data that will be written to the DataHandler. This is a modified version of the data in
$this->getRecordRepresentation()->getData()
.- Return type
array
- getDataHandler()¶
Returns the internal DataHandler object used in the operation.
- Return type
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.
- Return type
string
- getLanguage()¶
Returns the record language represented by a
\TYPO3\CMS\Core\Site\Entity\SiteLanguage
object, if set.$this->getRecordRepresentation()->getRecordInstanceIdentifier()->getLanguage()
- Return type
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
- Return type
array
- getRemoteId()¶
Returns the table name. Shortcut for
$this->getRecordRepresentation()->getRecordInstanceIdentifier()->getRemoteIdWithAspects()
- Return type
string
- getTable()¶
Returns the table name. Shortcut for
$this->getRecordRepresentation()->getRecordInstanceIdentifier()->getTable()
- Return type
string
- getRecordRepresentation()¶
- Return type
PixelantInterestDomainModelDtoRecordRepresentation
- getStoragePid()¶
Gets the PID of the record as originally set during object construction, usually by the
\Pixelant\Interest\DataHandling\Operation\Event\Handler\ResolveStoragePid
event.- Return type
void
- getSettings()¶
Returns the settings array from UserTS (
tx_interest.*
).- Return type
array
- getUid()¶
Returns the record UID, or zero if not yet set.
$this->getRecordRepresentation()->getRecordInstanceIdentifier()->getUid()
- Return type
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->getRecordRepresentation()->getRecordInstanceIdentifier()->getUidPlaceholder()
- Return type
string
- hasExecuted()¶
Returns true if the operation has executed the DataHandler operations.
- Return type
bool
- isDataFieldSet($fieldName)¶
- Parameters
$fieldName (
string
) --
Check if a field in the data array is set. Same as
isset($this->getDataForDataHandler()[$fieldName])
.- Return type
bool
- isSuccessful()¶
Returns true if the operation has executed the DataHandler operations without errors.
- Return type
bool
- retrieveMessage($message)¶
- Parameters
$messageFqcn (
string
) --
Pick the last message of class
$messageFqcn
from the message queue. Returns null if no messages are left in the queue.- Return type
PixelantInterestDataHandlingOperationMessageMessageInterface|null
- setDataFieldForDataHandler($fieldName, $value)¶
- Parameters
$fieldName (
string
) --
Set the value of a specific field in the data for DataHandler. Same as:
- Return type
void
- setDataForDataHandler($dataForDataHandler)¶
Set the data that will be written to the DataHandler.
- Parameters
$dataForDataHandler (
array
) --
- setHash($hash)¶
- Parameters
$hash (
string
) --
Override the record operation's uniqueness hash. Changing this value can have severe consequences for data integrity.
- Return type
void
- setStoragePid($storagePid)¶
- Parameters
$storagePid (
int
) --
Sets the storage PID. This might override a PID set by the
\Pixelant\Interest\DataHandling\Operation\Event\Handler\ResolveStoragePid
event, which usually handles this task.- Return type
void
- setUid($uid)¶
- Parameters
$uid (
int
) --
Sets the record UID.
$this->getRecordRepresentation()->getRecordInstanceIdentifier()->setUid($uid)
- Return type
void
- unsetDataField($fieldName)¶
- Parameters
$fieldName (
string
) --
Unset a field in the data array. Same as:
- Return type
void
Record Operation Messages¶
Classes implementing \Pixelant\Interest\DataHandling\Operation\Message\MessageInterface
can be used to carry information within the execution flow of an instance of \Pixelant\Interest\DataHandling\Operation\AbstractRecordOperation
. This is especially useful between EventHandlers.
For example, \Pixelant\Interest\DataHandling\Operation\Event\Handler\Message\PendingRelationMessage
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\Interest\DataHandling\Operation\Event\Handler\MapUidsAndExtractPendingRelations
:
Retrieving messages and using the message data to persist the information to the database in \Pixelant\Interest\DataHandling\Operation\Event\Handler\PersistPendingRelationInformation
:
Mapping table¶
The extension keeps track of the mapping between remote IDs and TYPO3 records in the table tx_interest_remote_id_mapping
. In addition to mapping information, the table contains metadata about each record.
Warning
You should never access the tx_interest_remote_id_mapping
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 Pixelant\Interest\Domain\Repository\RemoteIdMappingRepository¶
- touch($remoteId)¶
Touches the remote ID and nothing else. Sets the
touched
timestamp for the remote ID to the current time.- Parameters
$remoteId (
string
) -- The remote ID of the record to touch.
- touched($remoteId)¶
Returns the touched timestamp for the record.
- Parameters
$remoteId (
string
) -- The remote ID of the record to touch.
- Return type
int
- findAllUntouchedSince($timestamp, $excludeManual = true)¶
Returns an array containing all remote IDs that have not been touched since
$timestamp
.- Parameters
$timestamp (
int
) -- Unix timestamp.$excludeManual (
bool
) -- 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.
- Return type
bool
- findAllTouchedSince($timestamp, $excludeManual = true)¶
Returns an array containing all remote IDs that have been touched since
$timestamp
.- Parameters
$timestamp (
int
) -- Unix timestamp.$excludeManual (
bool
) -- 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.
- Return type
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\DataHandling\Operation\Event\Handler\ForeignRelationSortingEventHandler
File modification info by
\Pixelant\Interest\DataHandling\Operation\Event\Handler\PersistFileDataEventHandler
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 metaData
property or the --metaData
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 Pixelant\Interest\Domain\Repository\RemoteIdMappingRepository
- getMetaData($remoteId)¶
Retrieves all of the metadata entries as a key-value array.
- Parameters
$remoteId (
string
) -- The remote ID of the record to return the metadata for.
- Return type
array
- getMetaDataValue($remoteId, $key)¶
Retrieves a metadata entry.
- Parameters
$remoteId (
string
) -- The remote ID of the record to return the metadata for.$key (
string
) -- The originator class's fully qualified class name.
- Return type
string, float, int, array, or null
- getMetaDataValue($remoteId, $key, $value)
Sets a metadata entry.
- Parameters
$remoteId (
string
) -- The remote ID of the record to return the metadata for.$key (
string
) -- The originator class's fully qualified class name.$value (
string|float|int|array|null
) -- The value to set.
Example¶
This simplified excerpt from PersistFileDataEventHandler
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();