Fritz
Fritz

Reputation: 13

Can we force concrete classes to implement an interface specified in an abstract class in PHP?

Is it possible to specify an interface in an abstract class that must be implemented by concrete implementations in PHP?

At the moment we are doing something like the following (hopefully made simple enough to follow):

<?php

declare(strict_types=1);

interface SensorObserver
{
    public function sensorChanged( array $sensorData );
}

class SensorSubject
{
    protected $observers = [];

    public function addSensorObserver( SensorObserver $sensorObserver ) : bool
    {
        if ( !in_array( $sensorObserver, $this->observers ) )
        {
            $this->observers[] = $sensorObserver;
            return true;
        }
        return false;
    }

    public function removeSensorObserver( SensorObserver $sensorObserver ) : bool
    {
        if ( !in_array( $sensorObserver, $this->observers ) )
        {
            return false;
        }
        else
        {
            unset( $this->observers[array_search( $sensorObserver, $this->observers )]); 
            return true;
        }
    }

    public function notifyObservers( array $sensorData )
    {
        foreach( $this->observers as $observer )
        {
            $observer->sensorChanged( $sensorData );
        }
    }
}

abstract class GenericDisplay
{
    protected $name;
    protected $sensorData;
    protected $displayData;

    abstract public function showDisplay();
    abstract protected function generateDisplayData();

    public function __construct( string $name )
    {
        $this->name = $name;
        $this->sensorData = [];
        $this->displayData = [];
    }
}

class DisplayDeviceA extends GenericDisplay implements SensorObserver
{
    public function __construct()
    {
        parent::__construct( 'DisplayDeviceA' );
        $this->sensorData = [ 'SensorTypeA' => 0.0, 'SensorTypeB' => 0.0 ];
        $this->generateDisplayData();
    }

    public function showDisplay()
    {
        echo PHP_EOL . "{$this->name}: " . PHP_EOL;
        foreach ( $this->displayData as $key => $value )
        {
            echo "{$key}: {$value}" . PHP_EOL;
        }
    }

    protected function generateDisplayData()
    {
        // Complicated processing done for output here :)
        $this->displayData = $this->sensorData;
    }

    public function sensorChanged( array $sensorData )
    {
        $dirtySensorData = false;

        foreach( $sensorData as $key => $value )
        {
            if ( array_key_exists( $key, $this->sensorData ) )
            {
                // This is just an example, <imagine custom processing here  when sensor data changes />,
                // otherwise we could just implement in the base abstract class or perhaps with a trait
                $this->sensorData[$key] = $value;
                $dirtySensorData = true;
            }
        }

        if ( $dirtySensorData )
        {
            $this->generateDisplayData();
        }
    }
}

class DisplayDeviceB extends GenericDisplay implements SensorObserver
{
    public function __construct()
    {
        parent::__construct( 'DisplayDeviceB' );
        $this->sensorData = [ 'SensorTypeA' => 0.0, 'SensorTypeB' => 0.0, 'SensorTypeC' => 0.0 ];
        $this->generateDisplayData();
    }

    public function showDisplay()
    {
        echo PHP_EOL . "{$this->name}: " . PHP_EOL;
        foreach ( $this->displayData as $key => $value )
        {
            echo "{$key} => {$value}" . PHP_EOL;
        }
    }

    protected function generateDisplayData()
    {
        // Complicated processing done for output here :)
        $this->displayData = $this->sensorData;
    }

    public function sensorChanged( array $sensorData )
    {
        $dirtySensorData = false;

        foreach( $sensorData as $key => $value )
        {
            if ( array_key_exists( $key, $this->sensorData ) )
            {
                // Again, just an example...
                $this->sensorData[$key] = $value;
                $dirtySensorData = true;
            }
        }

        if ( $dirtySensorData )
        {
            $this->generateDisplayData();
        }
    }
}

class DisplayDeviceC extends GenericDisplay implements SensorObserver
{
    public function __construct()
    {
        parent::__construct( 'DisplayDeviceC' );
        $this->sensorData = [ 'SensorTypeB' => 0.0, 'SensorTypeD' => 0.0 ];
        $this->generateDisplayData();
    }

    public function showDisplay()
    {
        echo PHP_EOL . "{$this->name}: " . PHP_EOL;
        foreach ( $this->displayData as $key => $value )
        {
            echo "{$key} --> {$value}" . PHP_EOL;
        }
    }

    protected function generateDisplayData()
    {
        // Complicated processing done for output here :)
        $this->displayData = $this->sensorData;
    }

    public function sensorChanged( array $sensorData )
    {
        $dirtySensorData = false;

        foreach( $sensorData as $key => $value )
        {
            if ( array_key_exists( $key, $this->sensorData ) )
            {
                // Again, just an example...
                $this->sensorData[$key] = $value;
                $dirtySensorData = true;
            }
        }

        if ( $dirtySensorData )
        {
            $this->generateDisplayData();
        }
    }
}

$testDisplays = [ new DisplayDeviceA(), new DisplayDeviceB(), new DisplayDeviceC() ];
foreach( $testDisplays as $display )
{
    $display->showDisplay();
}

$sensorSubject = new SensorSubject();
foreach( $testDisplays as $display )
{
    $sensorSubject->addSensorObserver( $display );
}

$sensorSubject->notifyObservers( ['SensorTypeB' => 10.0, 'SensorTypeD' => 5.0] );

foreach( $testDisplays as $display )
{
    $display->showDisplay();
}

Wondering if there is essentially some way to write:

interface SensorObserver
{
    public function sensorChanged( array $sensorData );
}

abstract class GenericDisplay implements SensorObserver
{
    ...
    abstract public function sensorChanged( array $sensorData );
    ...
}

class ConcreteDisplay extends GenericDisplay
{
    public function sensorChanged( array $sensorData ) { ... };
}

Thanks to @Mark Baker and @Pael Petrov below. If anybody stumbles across this, a working example:

<?php

interface AnInterface
{
    public function foo();
}

abstract class AbstractClass implements AnInterface
{
    //...
}

class ConcreteClass extends AbstractClass
{
    public function foo()
    {
        echo 'foo';
    }
}


$concrete = new ConcreteClass();
$concrete->foo();

Upvotes: 1

Views: 61

Answers (1)

Pavel Petrov
Pavel Petrov

Reputation: 867

If an abstract class implements some interface concrete classes automatically implement all the interfaces of the abstract class so you just need to extend the abstract class:

class ConcreteDisplay extends GenericDisplay
{
    //implement interface methods here 
    //if not implemented in the parend class
}

This will do the job

Upvotes: 1

Related Questions