Refdata
Refdata (reference data) is used to define the accepted values for a particular field. These values are defined on the backend and do not change very often. We define an entity class for each field’s refdata containing the field’s possible values and also declare the field and its possible values in module/Application/config/refdata.config.php
.
Refdata Structure
Let’s look at an example of where we are using Refdata in Sirius. Here is a copy of the ReportStatus
class in module/Opg/src/Opg/Core/Model/Entity/AnnualReportLog/ReportStatus.php
:
<?php
namespace Opg\Core\Model\Entity\AnnualReportLog;
use Opg\Core\Model\Refdata;
final class ReportStatus extends Refdata
{
const CATEGORY = 'reportStatus';
const PENDING = 'PENDING';
const ACTIVE = 'ACTIVE';
const RECEIVED = 'RECEIVED';
const LODGED = 'LODGED';
const ABANDONED = 'ABANDONED';
public function isPending()
{
return self::PENDING === $this->getHandle();
}
public function isActive()
{
return self::ACTIVE === $this->getHandle();
}
public function isReceived()
{
return self::RECEIVED === $this->getHandle();
}
public function isLodged()
{
return self::LODGED === $this->getHandle();
}
public function isAbandoned()
{
return self::ABANDONED === $this->getHandle();
}
}
The values are defined as contants at the top of the class. Alongside this inside refdata.config.php
the label and handle attributes are also defined:
Entity\AnnualReportLog\ReportStatus::class => [
Entity\AnnualReportLog\ReportStatus::PENDING =>
['handle' => Entity\AnnualReportLog\ReportStatus::PENDING, 'label' => 'Pending'],
Entity\AnnualReportLog\ReportStatus::ACTIVE =>
['handle' => Entity\AnnualReportLog\ReportStatus::ACTIVE, 'label' => 'Active'],
Entity\AnnualReportLog\ReportStatus::RECEIVED =>
['handle' => Entity\AnnualReportLog\ReportStatus::RECEIVED, 'label' => 'Received'],
Entity\AnnualReportLog\ReportStatus::LODGED =>
['handle' => Entity\AnnualReportLog\ReportStatus::LODGED, 'label' => 'Lodged'],
Entity\AnnualReportLog\ReportStatus::ABANDONED =>
['handle' => Entity\AnnualReportLog\ReportStatus::ABANDONED, 'label' => 'Abandoned'],
],
Other attributes can also be defined if needed. For example OrderSubType
has an extra filters
attribute that is used to filter out subtypes that are supervised or not in this case:
Entity\CaseItem\Deputyship\OrderSubtype::class => [
Entity\CaseItem\Deputyship\OrderSubtype::NEW_DEPUTY =>
['handle' => Entity\CaseItem\Deputyship\OrderSubtype::NEW_DEPUTY, 'label' => 'New deputy', 'filters' => ['supervised']],
Entity\CaseItem\Deputyship\OrderSubtype::INTERIM_ORDER =>
['handle' => Entity\CaseItem\Deputyship\OrderSubtype::INTERIM_ORDER, 'label' => 'Interim order', 'filters' => ['supervised']],
Entity\CaseItem\Deputyship\OrderSubtype::REPLACEMENT =>
['handle' => Entity\CaseItem\Deputyship\OrderSubtype::REPLACEMENT, 'label' => 'Replacement', 'filters' => ['supervised']],
Entity\CaseItem\Deputyship\OrderSubtype::SUPPLEMENTARY =>
['handle' => Entity\CaseItem\Deputyship\OrderSubtype::SUPPLEMENTARY, 'label' => 'Supplementary'],
Entity\CaseItem\Deputyship\OrderSubtype::TENANCY =>
['handle' => Entity\CaseItem\Deputyship\OrderSubtype::TENANCY, 'label' => 'Tenancy'],
Entity\CaseItem\Deputyship\OrderSubtype::TRUSTEE =>
['handle' => Entity\CaseItem\Deputyship\OrderSubtype::TRUSTEE, 'label' => 'Trustee'],
Entity\CaseItem\Deputyship\OrderSubtype::VARIATION =>
['handle' => Entity\CaseItem\Deputyship\OrderSubtype::VARIATION, 'label' => 'Variation'],
Entity\CaseItem\Deputyship\OrderSubtype::DIRECTION =>
['handle' => Entity\CaseItem\Deputyship\OrderSubtype::DIRECTION, 'label' => 'Direction'],
],
Using Refdata
To get the refdata values for a particular field we make an api call to GET /api/v1/reference-data
and add a filter
parameter to the query string to tell it which field(s) we want. For example lets say we want all the possible values for OrderSubType
field if we look at the OrderSubType
class you will see a constant defined called CATEGORY
:
<?php
namespace Opg\Core\Model\Entity\CaseItem\Deputyship;
use Opg\Core\Model\Refdata;
final class OrderSubtype extends Refdata
{
const CATEGORY = 'orderSubtype';
We use this value: ‘orderSubtype’ as the filter parameter:
/api/v1/reference-data?filter=orderSubtype
and this will return all the orderSubTypes defined. You can also grab multiple refdata field values at once using CSV format: /api/v1/reference-data?filter=orderSubtype,howDeputyAppointed
Filtering Refdata Values
We saw the filters
attribute previously was used to mark orderSubType
values as ‘supervised’. We can call the same endpoint and apply the filter to only return these values:
/api/v1/reference-data?filter=orderSubtype:supervised
Deprecating Refdata Values
TODO: This approach needs to be implemented {: .notice–danger}
To mark a field’s value in refdata value as deprecated we add the attribute deprecated => true
to the value in refdata.config.php
eg:
Entity\AnnualReportLog\ReportStatus::class => [
Entity\AnnualReportLog\ReportStatus::PENDING =>
['handle' => Entity\AnnualReportLog\ReportStatus::PENDING, 'label' => 'Pending'],
Entity\AnnualReportLog\ReportStatus::ACTIVE =>
['handle' => Entity\AnnualReportLog\ReportStatus::ACTIVE, 'label' => 'Active'],
Entity\AnnualReportLog\ReportStatus::RECEIVED =>
['handle' => Entity\AnnualReportLog\ReportStatus::RECEIVED, 'label' => 'Received', 'deprecated' => true], // Shown here
Entity\AnnualReportLog\ReportStatus::LODGED =>
['handle' => Entity\AnnualReportLog\ReportStatus::LODGED, 'label' => 'Lodged'],
Entity\AnnualReportLog\ReportStatus::ABANDONED =>
['handle' => Entity\AnnualReportLog\ReportStatus::ABANDONED, 'label' => 'Abandoned'],
],
By default deprecated values are not included. To include them we add deprecated=true
to the query string:
/api/v1/reference-data?filter=orderSubtype&deprecated=true
Currently we do not completely remove refdata values due to the implications it can have to pre-existing data. There may be legacy records in the database that use refdata values that are now deprecated, but these will remain valid unless they are changed or a decision is made to run a migration to map all deprecated values to new ones.
How does Refdata work under the hood
RefdataListener
A class called RefdataListener
(module/Application/src/Application/Doctrine/Refdata/RefdataListener.php
) is instantiated and registered during the bootstrapping process of the app. The RefdataListener
handles conversion between Refdata value objects in the entity and strings inside the database. It operates on all fields defined as a Refdata type and requires the additional option refdataClass to be set.
On preFlush
hydrated refdata objects are converted to string values ready to be persisted to the database.
postLoad
does the opposite of preFlush
and hydrates values pulled from the database into their corresponding refdata objects.
RefdataService
The RefdataService
class is used to get the values inside refdata classes and map corresponding values contained in refdata.config.php
. The ReferenceDataController
calls getAllAsArray
from the RefdataService
passing in the class name of the refdata we’re looking for.