Как привести родительский класс к своему дочернему классу в PHP? - PullRequest
0 голосов
/ 26 июня 2018

Возможно ли привести родительский класс к своему дочернему в PHP? Если нет, то какова наиболее эффективная альтернатива этому?

Например, следующее приводит к ошибке:

<?php
class Foo {
    public $test;
    public static function create(): self {
        $a = new self();
        $a->test = 123;
        return $a;
    }
}

class Bar extends Foo {
    public $baz;
    public static function create(): self {
        $b = (self) parent::create(); // <--- Is this possible?
        $b->baz = 456;
        return $b;
    }
}

$bar = Bar::create();

var_dump($bar); /* should output:
  object(Bar)[35]
    public 'baz' => int 456
    public 'test' => int 123 */

Ответы [ 3 ]

0 голосов
/ 27 июня 2018

Когда вы объявляете, что Bar расширяет Foo, конструктор Foo по умолчанию вызывает конструктор Bar для инициализации его родительских свойств (в данном случае, public $test). Если вы определяете пользовательский конструктор для своих дочерних классов, вам также следует вызвать родительский конструктор. В этом разделе документации PHP описано, как это работает :

<?php
class BaseClass {
    function __construct() {
        print "In BaseClass constructor\n";
    }
}

class SubClass extends BaseClass {
    function __construct() {
        parent::__construct();
        print "In SubClass constructor\n";
    }
}

class OtherSubClass extends BaseClass {
    // inherits BaseClass's constructor
}

// In BaseClass constructor
$obj = new BaseClass();

// In BaseClass constructor
// In SubClass constructor
$obj = new SubClass();

// In BaseClass constructor
$obj = new OtherSubClass();

Для конкретной функциональности в вашем примере я бы предпочел поместить логику кода в защищенные конструкторы и вызывать их из фабричного метода:

<?php
class Foo {
    public $test;

    protected function __construct() {
        $this->test = 123;
    }

    public static function create(): Foo {
        return new self();
    }
}

class Bar extends Foo {
    public $baz;

    protected function __construct() {
        parent::__construct();
        $this->baz = 456;
    }

    public static function create(): Foo {
        return new self();
    }
}

Обратите внимание, что я изменил тип возвращаемого значения функции create(), чтобы он всегда был Foo. PHP не позволит вам изменять типы возвращаемых функций, перезаписываемых из родительского класса, и использование self в обоих случаях сделает это.

Однако это не проблема. Bar::create() дает нам Bar объект, настроенный так, как вы ожидаете. Тип возвращаемого значения просто должен быть совместим с Foo, а поскольку Bar реализует интерфейс Foo, он считается совместимым с типом.

Вы можете еще больше упростить этот код, используя ключевое слово static:

<?php
class Foo {
    public $test;

    protected function __construct() {
        $this->test = 123;
    }

    public static function create(): Foo {
        return new static();
    }
}

class Bar extends Foo {
    public $baz;

    protected function __construct() {
        parent::__construct();
        $this->baz = 456;
    }
}

var_dump(Bar::create()); /* Outputs:
    object(Bar)#1 (2) {
    ["baz"]=>
    int(456)
    ["test"]=>
    int(123)
    }
*/

Может случиться так, что использование защищенных конструкторов не вариант, например потому что у вас уже есть публичный конструктор с необходимыми аргументами или похожей ситуацией. В этом случае вам придется прибегнуть к тому, что объясняется в других ответах, создав временный Foo и скопировав значения свойств в новый Bar объект:

<?php
class Foo {
    public $test;

    public static function create(): Foo {
        $result = new self();
        $result->test = 123;
        return $result;
    }
}

class Bar extends Foo {
    public $baz;

    public static function create(): Foo {
        $foo = parent::create();
        $result = new self();
        foreach($foo as $property => $value) {
            $result->$property = $value;
        }
        $result->baz = 456;
        return $result;
    }
}

var_dump(Bar::create()); /* Outputs:
    object(Bar)#2 (2) {
    ["baz"]=>
    int(456)
    ["test"]=>
    int(123)
    }
*/
0 голосов
/ 09 августа 2018

Я смог реализовать это, используя следующую стратегию:

class Foo {
    public $test;
    public static function create(array $row): self {
        $a = new static();
        $a->test = $row['Test'];
        return $a;
    }
}

class Bar extends Foo {
    public $baz;
    public static function create(array $row): Foo {
        $b = parent::create($row);
        $b->baz = $row['Baz'];
        return $b;
    }
}

$test = Bar::create([
    'Test' => 123,
    'Baz' => 456,
]);

var_dump($test);
/* Outputs:
    object(Bar)#2 (2) {
        ["baz"]=> int(456)
        ["test"]=> int(123)
    } */
0 голосов
/ 26 июня 2018

Я не думаю, что есть что-то встроенное, что делает это. Вы можете написать функцию в дочернем классе, которая принимает экземпляр родительского класса и возвращает дочерний элемент с тем же содержимым.

class Bar extends Foo {
    public static function FooFromBar($foo) {
        $bar = new Bar();
        $bar->prop1 = $foo->prop1;
        $bar->prop2 = $foo->prop2;
        // continue for all properties
        return $bar;
    }
}
...