user1032531
user1032531

Reputation: 26331

Conditional argument and return type declarations (aka type hinting)

I have the following abstract class which has argument and return type declarations of the Entity object. Entity is a fictional placeholder, and in reality they should be declared to show returning User (or whatever the actual class which extends EntityServices specifies).

Is it possible to have EntityServices utilize type declarations of User instead of Entity without duplicating the script in the User class? If so, how? If not, is there a workaround that will allow the script to be reused with at least some level of type declaration functionality?

<?php
namespace NotionCommotion;
abstract class EntityService
{
    //Constructor in child

    public function get(int $id): ?Entity {
        //Will return User or Bla argument based on the extending class
        return $this->mapper->read($id);
    }

    public function create(array $data): Entity {
        //Will return User or Bla argument based on the extending class
        if (!$this->validator->load($this->getValidationFile())->isValid($data)) throw new UserValidationError($this->validator, $data);
        $this->doTransation(function(){$this->mapper->add($data);});
    }

    public function update(array $data, int $id): Entity {
        //Will return User or Bla argument based on the extending class
        if (!$this->validator->load($this->getValidationFile())->nameValueisValid($data)) throw new UserValidationError($this->validator, $data);
        $this->doTransation(function(){$this->mapper->update($data);});
    }

    public function delete(int $id): void {
        $this->mapper->delete($id);
    }

    public function whatever(Entity $whatever) {
        //Requires User or Bla argument based on the extending class
    }

    protected function doTransation($f){
        try {
            $f();
            $this->pdo->commit();
        } catch (\PDOException $e) {
            $this->pdo->rollBack();
            throw($e);
        }
    }

    abstract protected function getValidationFile();
}

UserServices class

<?php
namespace NotionCommotion\User;
class UserService extends \EntityService
{
    public function __construct(UserMapper $userMapper, \Validator $validator, Foo $foo) {
        $this->mapper=$userMapper;
        $this->validator=$validator;
        $this->foo=$foo;
    }
}

BlaServices class

<?php
namespace NotionCommotion\Bla;
class BlaService extends \EntityService
{
    public function __construct(BlaMapper $blaMapper, \Validator $validator) {
        $this->mapper=$blaMapper;
        $this->validator=$validator;
    }
}

Upvotes: 1

Views: 276

Answers (1)

yivi
yivi

Reputation: 47646

You can't do this in PHP.

Generally, a sane way of doing it would be just using inheritance or interfaces.

E.g, with inheritance:

class Animal {}

class Cat extends Animal {}

class Service {
  public function reproduce() : Animal {
    return new Animal();
  }
}


class ImprovedService extends Service {
  public function reproduce() : Animal {
    return new Cat();
  }
}

You can't change reproduce() definition, but you can return a descendant from the original return definition.

Or with interfaces, something like:

interface Mammal {
  public function makeSound() : string;
}

class Dog implements Mammal {
  public function makeSound() : string {
    return "Bark!";
  }
}

class GenericMammal implements Mammal {
  public function makeSound() : string {
    return "???";
  }
}

class Service {
  public function test() : Mammal {
    return new GenericMammal();
  }
}


class ImprovedService extends Service {
  public function test() : Mammal {
    return new Dog();
  }
}

Each method has advantages and disadvantages, so it would fall upon you to see which one makes better sense for your case.

Upvotes: 1

Related Questions