'Returning a child of class that is returned from a Trait
I then have a Base DTO class
class BaseDto
{
public function __construct(array $dtoValues)
{
$this->properties = array_map(static function (ReflectionProperty $q) {
return trim($q->name);
}, (new ReflectionClass(get_class($this)))->getProperties(ReflectionProperty::IS_PUBLIC));
foreach ($dtoValues as $propertyName => $value) {
$propertyName = Str::camel($propertyName);
if (in_array($propertyName, $this->properties, false)) {
$this->{$propertyName} = $value;
}
}
}
}
I also have an actual DTO class
class ModelDTO extends BaseDto
{
public int $id
public string $name;
}
I have the following Trait in PHP
trait ToDtoTrait
{
/**
* @param string $dtoClassName
* @return BaseDto
* @throws InvalidArgumentException
*/
public function toDto(string $dtoClassName): BaseDto;
{
$this->validateDtoClass($dtoClassName, BaseDto::class);
return new $dtoClassName($this->toArray());
}
/**
* @param string $dtoClassName
* @param string $baseClassName
* @return void
*/
private function validateDtoClass(string $dtoClassName, string $baseClassName)
{
if (!class_exists($dtoClassName)) {
throw new InvalidArgumentException("Trying to create a DTO for a class that doesn't exist: {$dtoClassName}");
}
if (!is_subclass_of($dtoClassName, $baseClassName)) {
throw new InvalidArgumentException("Can not convert current object to '{$dtoClassName}' as it is not a valid DTO class: " . self::class);
}
}
}
That trait is then used inside of my Model classes
class MyDbModel
{
use ToDtoTrait;
}
So this allows me to get an entry from the DB via the Model and then call toDto to receive an instance of the DTO class. Simple enough.
Now I have a service and this service will basically find the entry and return the DTO.
class MyService
{
public function find(int $id): ?ModelDTO
{
$model = MyModel::find($id);
if (empty($model)) {
return null;
}
return $model->toDto();
}
}
When I do it this way, I get a warning in the IDE:
Return value is expected to be '\App\Dtos\ModelDto|null', '\App\Dtos\BaseDto' returned
How do I declare this so that people can see that MyService::find() returns an instance of ModelDto so they will have autocomplete for the attributes of the DTO and any other base functions that come with it (not shown here).
Solution 1:[1]
The warning is raised because the return type of ToDtoTrait::toDto isBaseDto while the return type of MyService::find is ?ModelDTO, which are polymorphically incompatible (a BaseDto is not necessarily a ModelDTO).
An easy solution is to narrow down the DTO type using instanceof:
// MyService::find
$model = MyModel::find($id);
if (empty($model)) {
return null;
}
$dto = $model->toDto();
if (!$dto instanceof ModelDTO) {
return null;
}
return $dto;
Sidenote: Why is ToDtoTrait::toDto called without arguments in MyService (return $model->toDto();)? Looks like you want to pass ModelDTO::class to it: return $model->toDto(ModelDTO::class);.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Jeroen van der Laan |
