Home > Enterprise >  Type hinting an array key of a function argument
Type hinting an array key of a function argument

Time:07-15

Let's say I have a class that takes in an array:

class Database {
   function __construct($options = ['pdo' => '' ]) {
      $this->pdo = $options['pdo'];
   }
}

Is there a way to specify class for the array key? Maybe something like this?

class Database {
   function __construct($options = [\PDO 'pdo']) {
      $this->pdo = $options['pdo'];
   }
}

CodePudding user response:

PHP does not have strongly typed arrays.

What you can do instead is use a class or interface to represent the $options parameter

interface Options {
    public function pdo(): \PDO;
}

class Database {
    private \PDO $pdo;

    function __construct(Options $options) {
        $this->pdo = $options->pdo();
    }
}

CodePudding user response:

Actually it is not possible to type hint array keys with PHP. There are some possible solutions to this, but none of them will work with type hints on arrays.

Validating the options in the constructor

Since it is not possible to check the array keys for a specific type hint you can check with an if condition, if a specific option isset and if it is an instance of PDO.

class Database
{
    protected PDO $pdo;

    function __construct(array $options)
    {
        if (!isset($options['pdo']) || !$options['pdo'] instanceof PDO) {
            // error handling here
        }

        $this->pdo = $options['pdo'];
    }

}

Using an interface

The use of an interface is obvious here. Others, like e.g. Doctrine ORM or Laminas, use an aware interface to point out, that there is an instance of a specific class available.

interface PdoAwareInterface
{
    public function getPdo(): PDO;
    public function setPdo(PDO $pdo): void;
}

class Database implements PdoAwareInterface
{
    protected PDO $pdo;

    public function __construct(array $options = [])
    {
        ...
    }

    public function getPdo(): PDO
    {
        return $this->pdo;
    }

    public function setPdo(PDO $pdo): void
    {
        $this->pdo = $pdo;
    }
}

class DatabaseFactory 
{
    public function __invoke()
    {
        $pdo = new PDO(...);
    
        $database = new Database([]);
        $database->setPdo($pdo);

        return $database;
    }
}

As you can see the PDOAwareInterface is implemented. It points out, that a PDO instance is aware. You can use it e.g. in a factory, to set the pdo instance via the interface implemented methods.

Options Interface

Using in interface for the options itself is a bit messy, because here you cannot be sure whether the PDO instance was actually set before intializing the Database class. Anyway ... it could be a possible way.

interface DatabaseOptionsInterface
{
    public function getPdo(): PDO;
}

class DatabaseOptions implements DatabaseOptionsInterface
{
    protected PDO $pdo;

    public function __construct(PDO $pdo)
    {
        $this->pdo = $pdo;
    }

    public function getPdo(): PDO
    {
        return $this->pdo;
    }

}

class Database 
{
    protected PDO $pdo;

    public function __construct(DatabaseOptionsInterface $options)
    {
        $this->pdo = $options->getPdo();
    }
}

class DatabaseFactory
{
    public function __invoke()
    {
        $pdo = new PDO(...);
        $options = new DatabaseOptions($pdo);

        $database = new Database($options);

        return $database;
    }
}

Basically like initialisation with an AwareInterface, but via an additional DatabaseOptions instance.

Conclusion

Personally, I tend towards awareness, because here I can always check whether the object holds a certain instance ready. This would only be possible in a roundabout way via an options class, for example.

  •  Tags:  
  • php
  • Related