Revision #17 has been created by grigori on Jan 21, 2020, 9:45:55 AM with the memo:
added a note on case conversion
« previous (#16) next (#18) »
Changes
Title
unchanged
UUID instead of an auto-increment integer for ID with Active Record
Category
unchanged
How-tos
Yii version
unchanged
2.0
Tags
unchanged
mysql,active record,REST,UUID
Content
changed
> I have a dream ...
> I am happy to join with you today in what will go down in history as the
> greatest demonstration of
bad design of Active Record.[...]
```php
//check if value is a valid UUID
['id','match', 'pattern'=>'/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i'],
// convert UUID from text value to binary and store the text value in a private variable
,
// this is a workaround for lack of mapping in active record
['id','filter','skipOnError' => true, 'filter' => function($uuid) {[...]
if ($this->idText === NULL && $this->getIsNewRecord()){
//the filter did not convert ID to binary yet, return the data from input
return strtoupper($this->getAttribute('id')
);
}
//ID is converted
return
strtoupper($this->idText ?? $this->getAttribute('id_text')
);
}
```
Active Record does not callWhen we call the `$model->id` property we need the
`get
ter method if attributes contain the property. It should not be this way, so I return the default component behavior and make ID returned the right wayId()` executed. But Active Record base class overrides Yii compoent default behavior and does not call a getter method of an object if a property is a field in a table. So I override the magic getter.
From the other hand,
the firsta regexp valiator
I wrote calls `$model->id`
, triggering the getter before the UUID is saved to the private property
so I ne. I check if the object is newly created to serve the
text value f
rom user input.
It is strangeor validator.
Note the `strtoupper()` call: client may send UUID in both upper and low cases, but after unpacking from binary we will have a value in upper case. I received different string values before storing data to DB and after fetching it. Convert the textual UUID value to an upper or lower case everywhere to avoid problems.
It looks weird to mutate data in a validator, but I found this is the
onlybest way. I belive I shouldn't use `beforeSave()` callback to set the binary value for generating SQL, and return the text value back in `afterSave()` - supporting this code would be a classic hell like `#define true false;`.
Step 4. Define the mapping for output[...]
```
Step 5. Use Object Relation Mapping in Yii 3 when it's available and write simple mapping instead.mapping instead of these hacks.
P.S. A couple of helper functions.
```php
declare(strict_types=1);
namespace common\helpers;
class UUIDHelper
{
const UUID_REGEXP = '/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i';
public static function string2bin(string $uuid): string
{
return pack("H*", str_replace('-', '', $uuid));
}
public static function bin2string(string $binary): string
{
return strtolower(join("-", unpack("H8time_low/H4time_mid/H4time_hi/H4clock_seq_hi/H12clock_seq_low", $binary)));
}
public static function isUUID(string $uuid): bool
{
return (bool)preg_match(self::UUID_REGEXP,$uuid);
}
}
```