I'm trying to remove some code duplication that has proven to be prone to human errors.
I created a working sample code at https://3v4l.org/QFA6m#v8.2.7 and a demo of PHPStan failing where expected, https://phpstan.org/r/e6310a8c-6691-4096-a698-44eadb1ab1f2
On a high level:
- I am using DTO capable of creating an object of another class
interface CrudDto
definespublic function apply(): CrudEntity
class CreatePersonDto implements CrudDto
and itsapply()
method returns aPerson
class Person extends CrudEntity
- There is a
CrudManager
class capable of working with my DTO throughCrudManager::save(CrudDto $dto): CrudEntity
- Calling
$crudManager->save($dto)
returns whatever the given DTO can create:PersonCreateDto
->Person
,CatCreateDto
->Cat
and so on, you get the point.
I'm trying to tell CrudManager that it's not returning a CrudEntity
, but a Person
, or a Cat
, etc.
I achieved so by passing the expected class name to my save function and using things described here https://phpstan.org/blog/generics-by-examples#accept-a-class-string-and-return-the-object-of-that-type
But here comes the core of the question (thanks for reading until this point, btw!)
I would love to insulate the strict type checking so that the developer doesn't have to provide the expected type to the save()
method via a class-string<T of CrudEntity> $className
.
I want to do that because if the DTO knows what class it is creating, it should be able to give that information to whoever wants it.
I added CrudDto::belongsTo(): string
that returns the FQCN of whatever the entity the DTO can produce, and I can use that value to enforce the actual type on runtime, but I didn't find a way to inform static code analysers that the class-string is provided by the DTO itself, not by the argument next to it.
Effectively I'm looking for a pseudocode of something like this:
/**
* @template T of CrudEntity
* @param class-string<T> $dto::belongsTo()
*/
public function save(CrudDto $dto): CrudEntity;
In other words, I'd like to tell PHPStan, PHPStorm, psalm and others, that if CreatePersonDto::belongsTo()
returns Person::class
, calling $entityManager->save($personDto)
returns Person
, not just CrudDto
.
Can it be done without passing the expected return value's class name as a method argument of the save()
function?