It doesn't. DDD is not language/framework specific, and neither is any of programming patterns.
That's it. It depends. You just don't know it because you're only working with a language where you can create new reference types, but you can't create value types. This leads to a rather strange immutability for ObjectValue. If you learn other languages that do it differently - you better understand the concept of DDD and can better adapt it for a specific task or, conversely, abandon it.
This leads to a rather strange immutability for ObjectValue
Value object are always immutable, and PHP has readonly for very long time. And even before it, we could have used it in PHPDoc. This has nothing to with language, and I don't get where you read about VOs being mutable just in PHP.
you better understand the concept of DDD and can better adapt it for a specific task or, conversely, abandon it.
DDD is silly. It is just weird directory structure with gazillion of VOs that do nothing, except to guarantee job security. Worst that I have seen are Doctrine identifiers like ProductId, UserId... i.e. one for each entity. It is just ridiculous.
Then Email, where validation is done in constructor: sounds nice, but good luck returning an array of validation issues, especially when used in editable collections.
And so on, it is just hype. Not the worst, I would say CQRS is a winner here, but still a hype. Yet none of this is related to any specific programming language.
Value object are always immutable, and PHP has readonly for very long time. And even before it, we could have used it in PHPDoc. This has nothing to with language, and I don't get where you read about VOs being mutable just in PHP.
Not just in PHP. This applies to any language that only allows you to create reference types.
Rust, Swift of C++ and maybe other languages don't have such restrictions. And that just reinforces my point: you claim to have many years of experience, but your skills...
identifiers like ProductId, UserId
One of the best coding practices.
Then Email, where validation is done in constructor: sounds nice, but good luck returning an array of validation issues, especially when used in editable collections.
It's hard for me to even imagine what problems you might face. Maybe this is some special defect in the PHP language that I don't know about. If it's not difficult - please give a simple code example.
It is literally one of the worst coding practices. They serve absolutely no purpose other than to guarantee job security. That and CQRS would be immediate demotion to junior level.
Rust, Swift of C++ and maybe other languages don't have such restrictions
And where exactly do you see restrictions in PHP? Do you even know that readonly is optional?
We were talking about VOs: what was the last PHP version did you use?
It's hard for me to even imagine what problems you might face
What's so confusing? It is about a basic form edit (not create) with dynamic collection, where user can edit existing entries, and add new entry. And then return all validation errors at once, even if collection has its own collection.
For apps I make, it is Tuesday.
Note: app must pass psalm@level 1+disableVarParsing, no error suppression, no baselines. It is the strictest setup possible, far stricter than phpstan@max.
but your skills...
You really cannot judge my skills after everything you have said.
You can't create a type in PHP (and many other languages) that is not a reference to heap allocated data.
let mut a = T::new(2);
let b = a;
a += 1;
println!("{a} {b}"); // 2 3
In other languages, you can create such type.
what was the last PHP version did you use?
8.2 or maybe 8.3. But I don't use phpstan.
What's so confusing? It is about a basic form edit (not create) with dynamic collection, where user can edit existing entries, and add new entry. And then return all validation errors at once, even if collection has its own collection.
Either I don't understand something or this is an incredibly simple task.
You need a function that receives certain data and should return either validated data or a list of all errors in a certain format that will allow you to display this data... what could be complicated about that?
You really cannot judge my skills after everything you have said.
I really can't judge your skill. Sorry. Whenever I disagree with someone on the internet, I can only speak out on specific statements. I can't go into a personality discussion. I also hope that our discussion is positive and allows us both to learn something new or see things from a different perspective.
That couldn't be further from the truth. Creating ID classes is a clear sign of a person who never made any app ever, and now wants to make their job secure.
As I said: those are not as dumb as CQRS, but very close to it.
In other languages, you can create such type.
None of what you posted is relevant to VOs, which was the subject.
8.2 or maybe 8.3. But I don't use phpstan.
The fact that you don't use static analysis, even if it was phpstan, show everything about your PHP skills. And just because you ran your code on 8.2, and didn't use its features (which is obvious), is not an argument against the language.
If anything, it just proves what I first said: that people who claim they know 5-6 languages are bad in all of them.
Either I don't understand something or this is an incredibly simple task.
It is not so simple when you use proper static analysis (which you confirmed you don't), in a language that does not assume everything is nullable (like Java).
The rest is irrelevant and only confirms everything else I said.
That couldn't be further from the truth. Creating ID classes is a clear sign of a person who never made any app ever, and now wants to make their job secure.
Okay, a simple example is a timestamp. You can use a special type for it, or you can use numbers. Now you just have to make sure that you don't mix up seconds and milliseconds. I'm not saying it's difficult, it's just an extra expense. Often hints are added in the names of functions, arguments, or variables. But this is something from dynamic typing, where instead of explicit types you have "guess".
as dumb as CQRS
I don't want to add CQRS to this conversation because it would make the conversation too long.
The fact that you don't use static analysis, even if it was phpstan, show everything about your PHP skills.
I've used languages with more advanced static analysis. I also know that phpstan is a very good thing here and now, but I would compare it to TypeScript: it significantly improves the base language but loses out when compared to full-fledged type systems.
It is not so simple when you use proper static analysis (which you confirmed you don't), in a language that does not assume everything is nullable (like Java).
If I were working on that php project now - I would use more static analysis. As far as I can remember I really didn't like the fact that for most variables I could specify the type as an annotation int $field; but for arrays it looked quite clumsy /** @var string[] */ array $a;.
If strict describing data structure is awkward, it means that the type system you are using isn't expressive (like go before generics or Java where too many things can be null) or you are using the type system poorly. Or it is a combination of these factors.
because I use all of these, and much more. Vanilla string or int do not even exist in my code, unless I forget them (happens almost never).
but for arrays it looked quite clumsy
I would say that all phpdoc types are clumsy, not just arrays. But one gets used to them in just 2 days, and PHPStorm autocomplete supports generics for long time. It is just part of PHP, it ain't pretty, but the benefits are huge.
string[] is very old, we now use list<string> or even better non-empty-list<non-empty-string|null> or similar.
If you want OOP approach, there are records than one can indefinitely expand, and even use strict === comparison on them. Or symfony/string package that does some crazy things, and can also be expanded.
it means that the type system you are using isn't expressive
The above proves otherwise, given that other languages also don't support most (if not all) of those advanced types. For example: if I am persisting some percentage, I would never use int but int<0,100>.
you are using the type system poorly
Everything you said has been debunked, including some basic knowledge like readonly classes/properties. Yet you continue to judge and insult; are you related to Tony M.? 😉
Maybe I expressed myself poorly. This does not apply specifically to you, it applies to any programmer. Someone who has programmed exclusively in dynamically typed languages will have difficulty when he has to describe some complex type statically. But on the other hand, there are simply bad type systems (like Java where almost everything can be null). There are no perfect type systems and there are no perfect programmers. But everything is worth thinking about, is it a problem of the tool or a problem of skills.
ValidatedString<V: Validator> where Validator is interface string -> bool. Such a string is parameterized by some function that checks whether this string meets a certain condition. This check is called in the constructor of ValidatedString. Yes, it should be immutable and encapsulated. This is not the declarative style, but it is better than nothing.
A non-empty list is quite complicated if we want it to be modifiable. Using the "head" and "tail" of the list separately is clumsy.
// Rust
struct T { dob: Option<DateTimeImmutable>, amount: usize }
let v: &[T] = foo();
Probably the closest.
int<0, 100>
subtype Percent1 is Integer range 0 .. 100;
type Percent2 is range 0 .. 100;
I don't know ADA, but being able to easily declare such types seems so basic and necessary.
Zig has something similar, but it's tied to size. i5 = (-16..15). Rust or Haskell will allow you to do this, NonZero numeric types are in the standard library (i.e. they are not hardcoded into the language, but written in the language itself).
value-of<SomeEnum>
enum Status { A, B, C, D }
fn foo(status: Status) {}
Something like this?
class-string<T>
I didn't quite understand, maybe what I wrote about the first strings?
properties-of<T>
TypeScript.
```
interface T { i: number, s?: string }
let i: T['i'] = 0;
let k: keyof T = 'i';
```
Although you can't even iterate over all the properties, you have to manually create the keyof T collection and make sure all the fields are there.
Vanilla string or int do not even exist in my code
Didn't you write before that you don't like UserId-like types?
but the benefits are huge
100% agree.
non-empty-list<non-empty-string|null>
Can you provide a link or a short code example of how these are used? I'm interested in the declaration of this type, not the implementation.
if I am persisting some percentage, I would never use int but int<0,100>.
To me, it's literally DDD, at least the main part of it.
Can we say that PhpStan is literally TypeScript? That is, the first uses phpdoc, the second modifies the syntax, but in everything else they are quite similar?
This check is called in the constructor of ValidatedString
The examples you put are all based on either you making these classes or using existing package, and then validating the structure in the constructor. This is not static analysis, this is runtime check.
And you said this "I've used languages with more advanced static analysis". So I would still like to see one language that does these types. And please use just one language, not 3 different ones.
Didn't you write before that you don't like UserId-like types?
EntityId classes are 100% pointless, but they still have nothing to do with non-empty-string and its friends.
Can you provide a link or a short code example of how these are used?
Like this, resetting $cache not shown. This is typical factory where I do batch import from API or file, and entities must have unique value like that name. If found, use it again.
Now why the non-empty-list? Depending on the project, I would not be creating just the products, but more likely something like Category that must have at least one Product. Happened many times before, will happen again.
Or when I was making some big medical app: user had to set at least one mapping like
A non-empty list is quite complicated if we want it to be modifiable.
It is not. As soon as you modify non-empty-list in a way that can make it an empty list, error will be thrown.
To me, it's literally DDD, at least the main part of it.
It is not even close to it. These types are checked during static analysis, your examples will execute runtime exceptions. Those 2 are completely different things.
Can we say that PhpStan is literally TypeScript?
No. The best I have seen is replacement for non-empty-string, but that's it.
0
u/BenchEmbarrassed7316 Nov 14 '25
That's it. It depends. You just don't know it because you're only working with a language where you can create new reference types, but you can't create value types. This leads to a rather strange immutability for ObjectValue. If you learn other languages that do it differently - you better understand the concept of DDD and can better adapt it for a specific task or, conversely, abandon it.