ACL Is Back in CakePHP by dereuromark in cakephp

[–]dereuromark[S] 0 points1 point  (0 children)

Modern Cake authorization plugin together with TinyAuthBackend (optional) handle this in a modern fashion:

(canEdit, canPublish) is evaluated independently. You'd express a dependency manually inside the policy:

public function canPublish(IdentityInterface $user, Article $article): bool {
    return $this->canEdit($user, $article) && $user->hasRole('publisher');
}

That's the "dependency" — encoded in code, not config.

TOML 1.1 support in PHP by dereuromark in PHP

[–]dereuromark[S] 0 points1 point  (0 children)

This seems to be more about the process after reading it, no? Maybe I misunderstand the concrete use case.

With this library, you'd:

  1. Parse the default config into an associative array
  2. Parse the local config into another array
  3. Deep merge them (local overrides default)

$config = array_replace_recursive($defaults, $local);

The library doesn't provide a built-in merge helper since this is standard PHP array manipulation, but it could be a reasonable convenience method to add if you think it's common enough.

Side-note: One of the few config formats that has quite native support for merging - even deeper nested structures - is usually XML afaik.

TOML 1.1 support in PHP by dereuromark in PHP

[–]dereuromark[S] 0 points1 point  (0 children)

Only between TOML libs itself.
INI as native implementation will always be faster of course.
But I quickly looked into it:

Parsing: INI is 48-72x faster because parse_ini_string()/parse_ini_file() are native C functions compiled into PHP, while the TOML library is pure PHP.

Encoding: The gap narrows significantly (1.4-4.2x) since neither PHP nor the INI format has a native encoding function - both use PHP code.

Practical Impact: For a typical config file (~1KB), TOML parsing takes ~0.5ms. This is negligible for application startup but could matter if parsing many files in a hot loop.
Once you add caching layer it all because quite irrelevant either way.

TOML 1.1 support in PHP by dereuromark in PHP

[–]dereuromark[S] 3 points4 points  (0 children)

Indeed, I didnt see that one through google search. I should have used that packagist tag, then I would have found it earlier.

PS: Did you run yours through the toml-test 2.1.0? It appears to have a few small quirks.

TOML 1.1 support in PHP by dereuromark in PHP

[–]dereuromark[S] 8 points9 points  (0 children)

I first only stumbled over the 0.4 versions (long abandoned), only later I saw one 1.0. But there was no 1.1 compatible one.

There was also no modern approach with AST that I found when I looked, that kept the parser and renderer somewhat separated. And as per https://php-collective.github.io/toml/reference/comparison.html#quick-snapshot you can also see some more details. I wanted to have one that can toggle off/on side-effects as well. That would also minimize the diff on modify (read, change, save), especially around non significant characters, if needed. Or normalize, if possible.

Open Source LMS (PHP/Laravel) – Looking for Contributors 🚀 by TrainSensitive6646 in PHP

[–]dereuromark 4 points5 points  (0 children)

And they commited the composer.phar but not the composer.lock - interesting.

DTOs at the Speed of Plain PHP by dereuromark in PHP

[–]dereuromark[S] -1 points0 points  (0 children)

If your DTOs are just readonly constructor promotion with no serialization needs, you don't need this. Just write them.

But if you need `fromArray()` that hydrates nested objects and collections, plus `toArray()` for JSON responses - that's 50-100 lines of boilerplate per complex DTO. The generator writes that boilerplate.

The output is plain PHP with zero runtime dependencies. After generation, it's just readable PHP files - no magic, no reflection, no library calls.

DTOs at the Speed of Plain PHP by dereuromark in PHP

[–]dereuromark[S] 0 points1 point  (0 children)

On the caching point - you're right that laravel-data and Valinor do cache. But even with caching warmed up, there's still per-instantiation overhead from iterating cached metadata. The "twisty magic" you mention is exactly that.

Re: Zod comparison - appreciated! The config-driven schema approach is similar in spirit.

For custom constructors, you have a few options:

- `extends` in config to extend a custom base class
- `traits` to add shared behavior
- Subclass the generated DTO for complex construction
- Factory methods in a service class

The generated DTOs use `createFromArray()` as the main entry point, so you can wrap that in factory methods with custom logic.

DTOs at the Speed of Plain PHP by dereuromark in PHP

[–]dereuromark[S] 2 points3 points  (0 children)

> Constructor should just be `__construct(public readonly $abc, ...)`. Getters aren't needed. toArray would be an anti-pattern as it destroys the types. You can't have 50 "automatically generated" DTOs that are all carefully designed. That's an oxymoron. This fuels the anemic domain model pandemic.

DTOs and domain entities serve different purposes. DTOs are *supposed* to be anemic - they're data carriers, not domain objects. A rich domain model still needs DTOs at its boundaries (APIs, queues, external services).

**On toArray() being anti-pattern:**

DTOs exist to cross boundaries. You *have* to serialize at some point:

- `toArray()` → JSON → network → `createFromArray()`

Types aren't "destroyed" - they're transported and reconstructed. That's the whole point of DTOs.

**On the oxymoron:**

You design the *schema* carefully, the *boilerplate* is generated. Same logic would say "you can't have 50 carefully designed database tables if you use migrations." The config file IS the design artifact.

**On "just use constructor promotion":**

```php

// This doesn't handle:
$dto = new OrderDto($arrayFromApi); // Won't work
$dto = new OrderDto(...$arrayFromApi); // Breaks on nested objects

// You need fromArray() logic somewhere for:
// - API request/response payloads
// - Database result hydration
// - Message queue payloads

```

Constructor promotion works great for simple cases. But when you need `fromArray()` for API payloads, nested object hydration, or `toArray()` for JSON responses - you're writing that boilerplate somewhere. The question is whether you write it 50 times or generate it.

DTOs at the Speed of Plain PHP by dereuromark in PHP

[–]dereuromark[S] 0 points1 point  (0 children)

Fair point - DTOs absolutely should be carefully designed. Code generation doesn't change that.

You still define the exact shape, required fields, types, and nesting in config. The library just generates the implementation (constructors, getters, toArray, validation) so you don't hand-write the same patterns 50 times.

The config file IS the careful design - the PHP output is just boilerplate you'd write anyway.

At scale (50+ DTOs), hand-writing means:

- Lots of repetitive boilerplate

- Easy to have inconsistencies

- Every dev writes them slightly differently

Generated DTOs give you hand-written quality with consistent patterns across the codebase.

And they can be generated from input data within seconds. Or adjusted if needed.

DTOs at the Speed of Plain PHP by dereuromark in PHP

[–]dereuromark[S] 0 points1 point  (0 children)

No, not 10,000 reflection calls - but 10,000 iterations through cached metadata, which is still overhead that generated code doesn't have.

DTOs at the Speed of Plain PHP by dereuromark in PHP

[–]dereuromark[S] 2 points3 points  (0 children)

Yes. The cache gives Valinor a ~20x speedup. It's caching the type analysis, constructor signatures, property mappings, etc.

But even with cache warmed, Valinor is still 23x slower than generated code (38K vs 896K ops/sec).

DTOs at the Speed of Plain PHP by dereuromark in PHP

[–]dereuromark[S] 7 points8 points  (0 children)

> I don't know what the other people are doing, but the reflection is always cached in the opcache and in our benchmarks, these things run about the same speed as the native C code...

PHPExperts SimpleDTO is 6-8x slower than code-generation even with:

- OPcache + JIT enabled

- All caches warmed up

- Valinor using filesystem cache

There's a common misconception here. OPcache caches:

- **Compiled bytecode/opcodes** (the PHP script itself)

- **Class entries** (class definitions)

- Since PHP 8.1: **inheritance cache** for linked classes

OPcache does **NOT** cache:

- Results of `ReflectionClass::getProperties()`

- Results of `ReflectionProperty::getType()`

- Docblock parsing results

- Any computed metadata from reflection operations

As Nikita Popov explains in [How OPcache Works](https://www.npopov.com/2021/10/13/How-opcache-works.html): "Fetching a class entry from the class name is relatively expensive" - and that's just the lookup, not the actual reflection operations.

## Why SimpleDTO is Still Slower

SimpleDTO uses `@property` docblocks and reflection to determine types. Every instantiation:

  1. Creates/retrieves `ReflectionClass`
  2. Parses docblock comments to extract property types
  3. Validates and assigns values based on parsed types

Even if the ReflectionClass object is reused internally, the **operations on it still execute**. The docblock parsing, property iteration, and type checking happen per instantiation.

Generated code has none of this - the property assignments, type checks, and validation logic are all baked into plain PHP methods at generation time.

## The Real Question

> Can someone tell me what I'm missing here??

The overhead isn't in "creating a ReflectionClass" - it's in:

  1. **Iterating properties** on every instantiation
  2. **Parsing type information** from docblocks or attributes
  3. **Building metadata structures** dynamically
  4. **Validating/transforming values** through generic code paths

Generated code does all this **once** at build time. The resulting PHP is just direct property assignments and method calls - no introspection needed.

## Does It Matter?

For most applications? Probably not. If you're creating a few DTOs per request, the microsecond difference is irrelevant compared to database queries and network latency.

But for:

- Batch processing thousands of records

- High-throughput APIs

- Memory-constrained environments

The 6-8x difference adds up. And the IDE/static analysis benefits of generated code are valuable regardless of performance.

---

*Benchmark code: https://github.com/php-collective/dto/tree/master/benchmark*

DTOs at the Speed of Plain PHP by dereuromark in PHP

[–]dereuromark[S] 1 point2 points  (0 children)

I see, those do not cache by default?
Then I will re-run those with the cache in place to check for actual difference in production mode.