Skip to content

Conversation

puzzledpolymath
Copy link
Contributor

@puzzledpolymath puzzledpolymath commented Jul 16, 2025

🔍 What was changed

  • Added new behavior for Snowflake identifiers
  • Added defaults for configuring identifier values globally
  • Added test cases covering Snowflake identifiers
  • Added test cases for combining Snowflake, ULID and UUID identifiers
  • Documentation added to README detailing usage

🔔 Note

Pipelines will fail until the following PR's are merged and released.

📝 Checklist

💯 Test coverage
🟢 Psalm with empty baseline

  • How was this tested:
    • Unit tests added

@puzzledpolymath puzzledpolymath requested a review from a team July 16, 2025 07:00
@puzzledpolymath puzzledpolymath added the status:code review The pull request needs review. label Jul 16, 2025
@puzzledpolymath puzzledpolymath requested review from roxblnfk and removed request for a team July 16, 2025 07:04
@puzzledpolymath
Copy link
Contributor Author

@roxblnfk This is now passing after those couple of PR's were merged. Feedback welcomed


$modifier->setTypecast(
$registry->getEntity($this->role)->getFields()->get($this->field),
[$factory, 'createFromInteger'],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ORM schema should be serializable. That's why a static factory should be used here. Is it possible?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the ramsey/identifier package has taken a different direction and moved away from static classes/factories. Not a bad thing, but it likely means we’ll need to create our own static classes/methods for instantiating these factories 🥲

While I wasn’t aware of the serialisation requirement or that it was an issue, the way I originally implemented this seemed the most logical at the time, given some factories require constructor arguments.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry not at my desk at moment, it’s 3:20am here in Australia. But I’ll throw out any idea.

An option might be: define a static method within each behaviour class. E.g

public static function fromInteger(array $args);

Then there’s the issue of needing to supply the ramsey factories with behaviour values into its constructor. Perhaps we could support a third value in the array?

[self::class, 'fromInteger', [1, 2]

Or allow the callback method to be defined similar to how enum annotations are declared.

[self::class, 'fromInteger(1, 2)']

Obviously this would require some passing, and changes so that the supplied parameters are captured and provided back to the static method when invoked.

Would something like that work?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the ramsey/identifier package has taken a different direction and moved away from static classes/factories. Not a bad thing, but it likely means we’ll need to create our own static classes/methods for instantiating these factories 🥲

It's interesting why there is any state if we want to load already generated IDs. We won't know the state of generators for those IDs that are in the database in advance.

Copy link
Contributor Author

@puzzledpolymath puzzledpolymath Aug 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's interesting why there is any state if we want to load already generated IDs. We won't know the state of generators for those IDs that are in the database in advance.

I'm not seeing an issue when generating a schema. Or am I am misunderstanding the problem?

return [
    'account_role' => [
        ...
        Schema::TYPECAST => [
            'id' => [
                unserialize('O:51:\"Ramsey\\Identifier\\Snowflake\\GenericSnowflakeFactory\":6:{s:73:\"\0Ramsey\\Identifier\\Snowflake\\GenericSnowflakeFactory\0clockSequenceCounter\";i:0;s:66:\"\0Ramsey\\Identifier\\Snowflake\\GenericSnowflakeFactory\0nodeIdShifted\";i:0;s:64:\"\0Ramsey\\Identifier\\Snowflake\\GenericSnowflakeFactory\0epochOffset\";i:0;s:59:\"\0Ramsey\\Identifier\\Snowflake\\GenericSnowflakeFactory\0nodeId\";i:0;s:58:\"\0Ramsey\\Identifier\\Snowflake\\GenericSnowflakeFactory\0clock\";O:43:\"Ramsey\\Identifier\\Service\\Clock\\SystemClock\":0:{}s:61:\"\0Ramsey\\Identifier\\Snowflake\\GenericSnowflakeFactory\0sequence\";O:54:\"Ramsey\\Identifier\\Service\\Clock\\MonotonicClockSequence\":6:{s:68:\"\0Ramsey\\Identifier\\Service\\Clock\\MonotonicClockSequence\0defaultState\";s:12:\"c52375de3d50\";s:61:\"\0Ramsey\\Identifier\\Service\\Clock\\MonotonicClockSequence\0clock\";O:43:\"Ramsey\\Identifier\\Service\\Clock\\SystemClock\":0:{}s:61:\"\0Ramsey\\Identifier\\Service\\Clock\\MonotonicClockSequence\0cache\";O:45:\"Ramsey\\Identifier\\Service\\Cache\\InMemoryCache\":4:{s:52:\"\0Ramsey\\Identifier\\Service\\Cache\\InMemoryCache\0cache\";a:0:{}s:57:\"\0Ramsey\\Identifier\\Service\\Cache\\InMemoryCache\0defaultTtl\";i:120;s:56:\"\0Ramsey\\Identifier\\Service\\Cache\\InMemoryCache\0cacheSize\";i:100;s:52:\"\0Ramsey\\Identifier\\Service\\Cache\\InMemoryCache\0clock\";O:43:\"Ramsey\\Identifier\\Service\\Clock\\SystemClock\":0:{}}s:65:\"\0Ramsey\\Identifier\\Service\\Clock\\MonotonicClockSequence\0precision\";E:53:\"Ramsey\\Identifier\\Service\\Clock\\Precision:Millisecond\";s:68:\"\0Ramsey\\Identifier\\Service\\Clock\\MonotonicClockSequence\0initialValue\";N;s:72:\"\0Ramsey\\Identifier\\Service\\Clock\\MonotonicClockSequence\0initialValueUsed\";b:0;}}'),
                'createFromInteger',
            ],
            'accountId' => 'int',
            'parentId' => 'int',
            'name' => 'string',
            'description' => 'string',
            'locked' => 'bool',
            'createdAt' => 'datetime',
            'updatedAt' => 'datetime',
        ],
    ],
];

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the schema, there should be no function calls, especially unserialize. It should be an array that can be packed into a text cache format, like JSON.

Ideally, we need static factories.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for clarifying.

Image below depicts some changes to Cycle\ORM\Parser\Typecast which I think might aid in working around this problem with serialization.

image

Then within the base classes, instead of something like:

            $factory = $this->snowflakeFactory();

            $modifier->setTypecast(
                $registry->getEntity($this->role)->getFields()->get($this->field),
                [$factory, 'createFromInteger'],
            );

You might do:

            $modifier->setTypecast(
                $registry->getEntity($this->role)->getFields()->get($this->field),
                [self::class, 'fromInteger', $this->getListenerArgs()],
            );

Where the fromInteger method looks something like.

    public static function fromInteger(
        int|string $identifier,
        DatabaseInterface $database,
        array $arguments
    ): \Ramsey\Identifier\Snowflake
    {
        return (new GenericSnowflakeFactory(
            $arguments['node'],
            $arguments['epochOffset']
        ))->createFromInteger($identifier);
    }

All theory and completely untested. Just wanted to run the idea by you first before taking it any further.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@puzzledpolymath
Copy link
Contributor Author

While recent changes allowing arguments to be passed too and from Typecast rules is working well, I still see an issue with the following sections of code.

https://github.com/cycle/entity-behavior-identifier/pull/5/files#diff-74064c5cec05ef4e5c7cd0bb6b5c863d6442bfdd80a79156894d90a62bcfe0a8R87-R88

https://github.com/cycle/entity-behavior-identifier/pull/5/files#diff-74064c5cec05ef4e5c7cd0bb6b5c863d6442bfdd80a79156894d90a62bcfe0a8R53-R54

When passing getTypecastArgs along with the callable, we are no longer checking for null values and using sane defaults. Previously fallback values where considered within the identifier classes __construct method and I had abstracted storing defaults in individual classes,, ensuring strict typing and providing getter methods for each value stored as a fallback.

But since merging your changes which did away with the individual classes, choosing to instead handle this within the listener classes, we no longer have access to the values. So I think we either need to go back to my original code with individual classes, or expose the static values stores in the listener classes.

https://github.com/cycle/entity-behavior-identifier/pull/5/files#diff-1a2f54417aeb09d554c0994516b95a469b8e42cdf2dd15d89e186d7c3c383c0eR49-R56

@roxblnfk
Copy link
Member

roxblnfk commented Aug 20, 2025

I plan not to use Factories and want to create identifiers using constructors

@puzzledpolymath
Copy link
Contributor Author

I plan not to use Factories and want to create identifiers using constructors

Okay ping me if there's anything else I can contribute here

@roxblnfk
Copy link
Member

roxblnfk commented Sep 1, 2025

I'm just waiting this PR cycle/orm#528 after that I can create an ORM release with new typecast factories

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status:code review The pull request needs review.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants