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\i18n;
|
||||||
use SilverStripe\i18n\i18nEntityProvider;
|
use SilverStripe\i18n\i18nEntityProvider;
|
||||||
use SilverStripe\ORM\Connect\MySQLSchemaManager;
|
use SilverStripe\ORM\Connect\MySQLSchemaManager;
|
||||||
use SilverStripe\ORM\FieldType\DBClassName;
|
|
||||||
use SilverStripe\ORM\FieldType\DBEnum;
|
|
||||||
use SilverStripe\ORM\FieldType\DBComposite;
|
use SilverStripe\ORM\FieldType\DBComposite;
|
||||||
use SilverStripe\ORM\FieldType\DBDatetime;
|
use SilverStripe\ORM\FieldType\DBDatetime;
|
||||||
|
use SilverStripe\ORM\FieldType\DBEnum;
|
||||||
use SilverStripe\ORM\FieldType\DBField;
|
use SilverStripe\ORM\FieldType\DBField;
|
||||||
use SilverStripe\ORM\Filters\SearchFilter;
|
use SilverStripe\ORM\Filters\SearchFilter;
|
||||||
use SilverStripe\ORM\Queries\SQLDelete;
|
use SilverStripe\ORM\Queries\SQLDelete;
|
||||||
use SilverStripe\ORM\Queries\SQLInsert;
|
|
||||||
use SilverStripe\ORM\Search\SearchContext;
|
use SilverStripe\ORM\Search\SearchContext;
|
||||||
|
use SilverStripe\ORM\UniqueKey\UniqueKeyInterface;
|
||||||
|
use SilverStripe\ORM\UniqueKey\UniqueKeyService;
|
||||||
use SilverStripe\Security\Member;
|
use SilverStripe\Security\Member;
|
||||||
use SilverStripe\Security\Permission;
|
use SilverStripe\Security\Permission;
|
||||||
use SilverStripe\Security\Security;
|
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 = "")
|
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));
|
$cacheKey = md5(serialize($cacheComponents));
|
||||||
|
|
||||||
$item = null;
|
$item = null;
|
||||||
@ -4186,6 +4187,28 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
return $added;
|
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
|
* Merge single object into a list, but ensures that existing objects are not
|
||||||
* re-added.
|
* re-added.
|
||||||
@ -4211,4 +4234,21 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
$this->mergeRelatedObject($list, $added, $joined);
|
$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