mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
parent
6bd0697a71
commit
7dc6b36c16
6
_config/unique-id.yml
Normal file
6
_config/unique-id.yml
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
Name: unique-id
|
||||
---
|
||||
SilverStripe\Core\Injector\Injector:
|
||||
SilverStripe\ORM\UniqueKey\UniqueKeyInterface:
|
||||
class: SilverStripe\ORM\UniqueKey\UniqueKeyService
|
48
docs/en/02_Developer_Guides/01_Templates/10_Unique_Keys.md
Normal file
48
docs/en/02_Developer_Guides/01_Templates/10_Unique_Keys.md
Normal file
@ -0,0 +1,48 @@
|
||||
---
|
||||
title: Generating Unique Keys
|
||||
summary: Outputting unique keys in templates.
|
||||
icon: code
|
||||
---
|
||||
|
||||
# Unique Keys
|
||||
|
||||
There are several cases where you may want to generate a unique key. For example:
|
||||
|
||||
* populate `ID` attribute in your HTML output
|
||||
* key for partial cache
|
||||
|
||||
This can be done simply by including following code in your template:
|
||||
|
||||
```ss
|
||||
$DataObject.UniqueKey
|
||||
```
|
||||
|
||||
`getUniqueKey` method is available on `DataObject` so you can use it on many object types like pages and blocks.
|
||||
|
||||
## Customisation
|
||||
|
||||
The unique key generation can be altered in two ways:
|
||||
|
||||
* you can provide extra data to be used when generating a key via an extension
|
||||
* you can inject over the key generation service and write your own custom code
|
||||
|
||||
### Extension point
|
||||
|
||||
`cacheKeyComponent` extension point is located in `DataObject::getUniqueKeyComponents`.
|
||||
Use standard extension flow to define the `cacheKeyComponent` method on your extension which is expected to return a `string`.
|
||||
This value will be used when unique key is generated. Common cases are:
|
||||
|
||||
* versions - object in different version stages needs to have different unique keys
|
||||
* locales - object in different locales needs to have different unique keys
|
||||
|
||||
### Custom service
|
||||
|
||||
`UniqueKeyService` is used by default but you can use injector to override it with your custom service. For example:
|
||||
|
||||
```yaml
|
||||
SilverStripe\Core\Injector\Injector:
|
||||
SilverStripe\ORM\UniqueKey\UniqueKeyService:
|
||||
class: App\Service\MyCustomService
|
||||
```
|
||||
|
||||
Your custom service has to implement `UniqueKeyInterface`.
|
@ -18,15 +18,15 @@ use SilverStripe\Forms\FormScaffolder;
|
||||
use SilverStripe\i18n\i18n;
|
||||
use SilverStripe\i18n\i18nEntityProvider;
|
||||
use SilverStripe\ORM\Connect\MySQLSchemaManager;
|
||||
use SilverStripe\ORM\FieldType\DBClassName;
|
||||
use SilverStripe\ORM\FieldType\DBEnum;
|
||||
use SilverStripe\ORM\FieldType\DBComposite;
|
||||
use SilverStripe\ORM\FieldType\DBDatetime;
|
||||
use SilverStripe\ORM\FieldType\DBEnum;
|
||||
use SilverStripe\ORM\FieldType\DBField;
|
||||
use SilverStripe\ORM\Filters\SearchFilter;
|
||||
use SilverStripe\ORM\Queries\SQLDelete;
|
||||
use SilverStripe\ORM\Queries\SQLInsert;
|
||||
use SilverStripe\ORM\Search\SearchContext;
|
||||
use SilverStripe\ORM\UniqueKey\UniqueKeyInterface;
|
||||
use SilverStripe\ORM\UniqueKey\UniqueKeyService;
|
||||
use SilverStripe\Security\Member;
|
||||
use SilverStripe\Security\Permission;
|
||||
use SilverStripe\Security\Security;
|
||||
@ -3234,9 +3234,10 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
*/
|
||||
public static function get_one($callerClass, $filter = "", $cache = true, $orderby = "")
|
||||
{
|
||||
$SNG = singleton($callerClass);
|
||||
/** @var DataObject $singleton */
|
||||
$singleton = singleton($callerClass);
|
||||
|
||||
$cacheComponents = [$filter, $orderby, $SNG->extend('cacheKeyComponent')];
|
||||
$cacheComponents = [$filter, $orderby, $singleton->getUniqueKeyComponents()];
|
||||
$cacheKey = md5(serialize($cacheComponents));
|
||||
|
||||
$item = null;
|
||||
@ -4186,6 +4187,28 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
return $added;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a unique key for data object
|
||||
* the unique key uses the @see DataObject::getUniqueKeyComponents() extension point so unique key modifiers
|
||||
* such as versioned or fluent are covered
|
||||
* i.e. same data object in different stages or different locales will produce different unique key
|
||||
*
|
||||
* recommended use:
|
||||
* - when you need unique key for caching purposes
|
||||
* - when you need unique id on the front end (for example JavaScript needs to target specific element)
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getUniqueKey(): string
|
||||
{
|
||||
/** @var UniqueKeyInterface $service */
|
||||
$service = Injector::inst()->get(UniqueKeyInterface::class);
|
||||
$keyComponents = $this->getUniqueKeyComponents();
|
||||
|
||||
return $service->generateKey($this, $keyComponents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge single object into a list, but ensures that existing objects are not
|
||||
* re-added.
|
||||
@ -4211,4 +4234,21 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$this->mergeRelatedObject($list, $added, $joined);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension point to add more cache key components.
|
||||
* The framework extend method will return combined values from DataExtension method(s) as an array
|
||||
* The method on your DataExtension class should return a single scalar value. For example:
|
||||
*
|
||||
* public function cacheKeyComponent()
|
||||
* {
|
||||
* return (string) $this->owner->MyColumn;
|
||||
* }
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getUniqueKeyComponents(): array
|
||||
{
|
||||
return $this->extend('cacheKeyComponent');
|
||||
}
|
||||
}
|
||||
|
23
src/ORM/UniqueKey/UniqueKeyInterface.php
Normal file
23
src/ORM/UniqueKey/UniqueKeyInterface.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\ORM\UniqueKey;
|
||||
|
||||
use SilverStripe\ORM\DataObject;
|
||||
|
||||
/**
|
||||
* Interface UniqueKeyInterface
|
||||
*
|
||||
* Useful when you want to implement your own custom service and use it instead of the default one (@see UniqueKeyService)
|
||||
* your custom service needs to implement this interface
|
||||
*/
|
||||
interface UniqueKeyInterface
|
||||
{
|
||||
/**
|
||||
* Generate a unique key for data object
|
||||
*
|
||||
* @param DataObject $object
|
||||
* @param array $keyComponents
|
||||
* @return string
|
||||
*/
|
||||
public function generateKey(DataObject $object, array $keyComponents = []): string;
|
||||
}
|
40
src/ORM/UniqueKey/UniqueKeyService.php
Normal file
40
src/ORM/UniqueKey/UniqueKeyService.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\ORM\UniqueKey;
|
||||
|
||||
use Exception;
|
||||
use SilverStripe\Core\ClassInfo;
|
||||
use SilverStripe\Core\Injector\Injectable;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
|
||||
/**
|
||||
* Class UniqueKeyService
|
||||
*
|
||||
* Generate a unique key for data object
|
||||
*
|
||||
* recommended use:
|
||||
* - when you need unique key for caching purposes
|
||||
* - when you need unique id on the front end (for example JavaScript needs to target specific element)
|
||||
*/
|
||||
class UniqueKeyService implements UniqueKeyInterface
|
||||
{
|
||||
use Injectable;
|
||||
|
||||
/**
|
||||
* @param DataObject $object
|
||||
* @param array $keyComponents key components are expected to be strings (or at least scalar values)
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public function generateKey(DataObject $object, array $keyComponents = []): string
|
||||
{
|
||||
$id = $object->isInDB() ? (string) $object->ID : bin2hex(random_bytes(16));
|
||||
$class = ClassInfo::shortName($object);
|
||||
$keyComponents = json_encode($keyComponents);
|
||||
$hash = md5($keyComponents . $object->ClassName . $id);
|
||||
|
||||
// note: class name and id are added just for readability as the hash already contains all parts
|
||||
// needed to create a unique key
|
||||
return sprintf('%s-%s-%s', $class, $id, $hash);
|
||||
}
|
||||
}
|
14
tests/php/ORM/UniqueKey/ExtraKeysExtension.php
Normal file
14
tests/php/ORM/UniqueKey/ExtraKeysExtension.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Tests\ORM\UniqueKey;
|
||||
|
||||
use SilverStripe\Core\Extension;
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
|
||||
class ExtraKeysExtension extends Extension implements TestOnly
|
||||
{
|
||||
public function cacheKeyComponent(): string
|
||||
{
|
||||
return 'extra-key';
|
||||
}
|
||||
}
|
21
tests/php/ORM/UniqueKey/Mountain.php
Normal file
21
tests/php/ORM/UniqueKey/Mountain.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Tests\ORM\UniqueKey;
|
||||
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
|
||||
class Mountain extends DataObject implements TestOnly
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private static $table_name = 'UniqueKeyTest_Mountain';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private static $db = [
|
||||
'Title' => 'Varchar',
|
||||
];
|
||||
}
|
21
tests/php/ORM/UniqueKey/River.php
Normal file
21
tests/php/ORM/UniqueKey/River.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Tests\ORM\UniqueKey;
|
||||
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
|
||||
class River extends DataObject implements TestOnly
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private static $table_name = 'UniqueKeyTest_River';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private static $db = [
|
||||
'Title' => 'Varchar',
|
||||
];
|
||||
}
|
56
tests/php/ORM/UniqueKey/ServiceTest.php
Normal file
56
tests/php/ORM/UniqueKey/ServiceTest.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Tests\ORM\UniqueKey;
|
||||
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
|
||||
class ServiceTest extends SapphireTest
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected static $extra_dataobjects = [
|
||||
River::class,
|
||||
Mountain::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @param string $class
|
||||
* @param bool $extraKeys
|
||||
* @param string $expected
|
||||
* @dataProvider uniqueKeysProvider
|
||||
*/
|
||||
public function testUniqueKey(int $id, string $class, bool $extraKeys, string $expected): void
|
||||
{
|
||||
if ($extraKeys) {
|
||||
$class::add_extension(ExtraKeysExtension::class);
|
||||
}
|
||||
|
||||
/** @var DataObject $object */
|
||||
$object = Injector::inst()->create($class);
|
||||
$object->ID = $id;
|
||||
|
||||
$this->assertEquals($expected, $object->getUniqueKey());
|
||||
|
||||
if ($extraKeys) {
|
||||
$class::remove_extension(ExtraKeysExtension::class);
|
||||
}
|
||||
}
|
||||
|
||||
public function uniqueKeysProvider(): array
|
||||
{
|
||||
return [
|
||||
[1, River::class, false, 'River-1-8d3310e232f75a01f5a0c9344655263d'],
|
||||
[1, River::class, true, 'River-1-ff2ea6e873a9e28538dd4af278f35e08'],
|
||||
[2, River::class, false, 'River-2-c562c31e5c2caaabb124b46e274097c1'],
|
||||
[2, River::class, true, 'River-2-410c1eb12697a26742bbe4b059625ab2'],
|
||||
[1, Mountain::class, false, 'Mountain-1-93164c0f65fa28778fb75163c1e3e2f0'],
|
||||
[1, Mountain::class, true, 'Mountain-1-2daf208e0b89252e5d239fbc0464a517'],
|
||||
[2, Mountain::class, false, 'Mountain-2-62366f2b970a64de6f2a8e8654f179d5'],
|
||||
[2, Mountain::class, true, 'Mountain-2-a724046b14d331a1486841eaa591d109'],
|
||||
];
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user