PHP抽象属性


126

有没有办法在PHP中定义抽象类属性?

abstract class Foo_Abstract {
    abstract public $tablename;
}

class Foo extends Foo_Abstract {
    //Foo must 'implement' $property
    public $tablename = 'users';   
}

Answers:


154

没有定义属性的事情。

您只能声明属性,因为它们是初始化时保留在内存中的数据容器。

另一方面,可以在不定义函数的情况下声明函数(类型,名称,参数)(缺少函数体),因此可以将其抽象化。

“摘要”仅表示已声明但未定义的内容,因此在使用前,您需要先定义它,否则它将变得无用。


58
没有明显的理由不能将“抽象”一词用于静态属性-含义稍有不同。例如,它可能表明子类必须为属性提供值。
frodeborli 2014年

2
在TypeScript中,有抽象属性和访问器。可悲的是,在php中这是不可能的。
ИльяЗеленько

52

不,没有办法用编译器强制执行,您必须对$tablename变量使用运行时检查(例如,在构造函数中),例如:

class Foo_Abstract {
  public final function __construct(/*whatever*/) {
    if(!isset($this->tablename))
      throw new LogicException(get_class($this) . ' must have a $tablename');
  }
}

要对Foo_Abstract的所有派生类强制执行此操作,必须制作Foo_Abstract的构造函数final,以防止覆盖。

您可以声明一个抽象的getter:

abstract class Foo_Abstract {
  abstract public function get_tablename();
}

class Foo extends Foo_Abstract {
  protected $tablename = 'tablename';
  public function get_tablename() {
    return $this->tablename;
  }
}

不错的功能,我喜欢您实现抽象属性的方式。
Mathieu Dumoulin

4
这将要求您使构造函数在抽象基类中最终化。
hakre 2011年

3
一些解释:如果在构造函数内部进行检查,并且应该是强制性的,则需要确保在每个实例实例上都执行该检查。因此,您需要防止将其删除,例如,通过扩展类并替换构造函数。您可以使用final关键字
哈克(Hakre)2011年

1
我喜欢“抽象吸气剂”解决方案。在类抽象中声明函数时,必须声明类本身抽象。这意味着除非扩展并完全实现,否则该类不可用。扩展该类时,必须提供“ getter”功能的实现。这意味着您还必须在扩展类内部创建一个相关属性,因为该函数必须要返回一些东西。按照这种模式,您将获得与声明抽象属性相同的结果,这也是一种干净清晰的方法。实际就是这样。
Salivan

1
使用抽象的getter还可以使您通过生成一个值(而不是返回一个常量)来实现它。抽象属性不允许这样做,尤其是静态属性。
托比亚

27

如果要在子对象中强制声明抽象对象属性,则取决于属性的上下文,我喜欢static在抽象对象构造函数或setter / getter方法中使用带有关键字的常量作为属性。您可以选择使用final来防止该方法在扩展类中被覆盖。

除此之外,子对象将覆盖父对象的属性和方法(如果重新定义)。例如,如果protected在父级中声明一个属性,在子级中声明一个属性public,则结果属性是公共的。但是,如果private在父级中声明了该属性,则该属性将保留private并且对子级不可用。

http://www.php.net//manual/en/language.oop5.static.php

abstract class AbstractFoo
{
    public $bar;

    final public function __construct()
    {
       $this->bar = static::BAR;
    }
}

class Foo extends AbstractFoo
{
    //const BAR = 'foobar';
}

$foo = new Foo; //Fatal Error: Undefined class constant 'BAR' (uncomment const BAR = 'foobar';)
echo $foo->bar;

4
最优雅的解决方案
Jannie Theunissen

24

如上所述,没有这样的确切定义。但是,我使用这种简单的解决方法来强制子类定义“抽象”属性:

abstract class Father 
{
  public $name;
  abstract protected function setName(); // now every child class must declare this 
                                      // function and thus declare the property

  public function __construct() 
  {
    $this->setName();
  }
}

class Son extends Father
{
  protected function setName()
  {
    $this->name = "son";
  }

  function __construct(){
    parent::__construct();
  }
}

优雅,但不能解决static属性问题。
罗伯特2014年

1
我认为您不能为抽象方法设置私有。
Zorji 2015年

据我了解,@ Phate01在评论本身中指出the only "safe" methods to have in a constructor are private and/or final ones,我的解决方法不是这种情况吗?我在里面使用私人
物品

4
这看起来不错,但不会强制子类实际设置$name。您可以在setName()没有实际设置的情况下实现该功能$name
JohnWE '16

3
我认为使用getName代替$name效果更好。abstract class Father { abstract protected function getName(); public function foo(){ echo $this->getName();} }
哈米德(Hamid)2016年

7

我今天问过自己同样的问题,我想加两分钱。

我们想要abstract属性的原因是要确保子类定义它们,并在未定义时抛出异常。在我的特定情况下,我需要一些可以与static盟友一起使用的东西。

理想情况下,我想要这样的东西:

abstract class A {
    abstract protected static $prop;
}

class B extends A {
    protected static $prop = 'B prop'; // $prop defined, B loads successfully
}

class C extends A {
    // throws an exception when loading C for the first time because $prop
    // is not defined.
}

我最终完成了该实施

abstract class A
{
    // no $prop definition in A!

    public static final function getProp()
    {
        return static::$prop;
    }
}

class B extends A
{
    protected static $prop = 'B prop';
}

class C extends A
{
}

如您所见,A我没有定义$prop,但是我在static吸气剂中使用它。因此,以下代码有效

B::getProp();
// => 'B prop'

$b = new B();
$b->getProp();
// => 'B prop'

C,在另一方面,我并不确定$prop,所以我得到的例外:

C::getProp();
// => Exception!

$c = new C();
$c->getProp();
// => Exception!

我必须调用该getProp() 方法来获取异常,并且在类加载时无法获取该异常,但至少在我看来,它与所需的行为非常接近。

我的定义getProp()final为了避免某些聪明的人(六个月内又名我自己)被诱惑去做

class D extends A {
    public static function getProp() {
        // really smart
    }
}

D::getProp();
// => no exception...

这是非常巧妙的hack。希望将来不需要这样做。
CMCDragonkai

6

正如您可以通过测试代码来发现的:

致命错误:无法在第3行的...中将属性声明为抽象

不,那里没有。属性不能在PHP中声明为抽象。

但是,您可以实现getter / setter函数摘要,这可能就是您想要的。

属性未实现(尤其是公共属性),它们仅存在(或不存在):

$foo = new Foo;
$foo->publicProperty = 'Bar';

6

对抽象属性的需求可以指示设计问题。虽然许多答案都实现了一种Template方法模式,并且可以正常工作,但它看起来总是很奇怪。

让我们看一下原始示例:

abstract class Foo_Abstract {
    abstract public $tablename;
}

class Foo extends Foo_Abstract {
    //Foo must 'implement' $property
    public $tablename = 'users';   
}

标记事物abstract是表示它必须具备的功能。好吧,必须具有的值(在这种情况下)是必需的依赖项,因此应在实例化期间将其传递给构造函数

class Table
{
    private $name;

    public function __construct(string $name)
    {
        $this->name = $name;
    }

    public function name(): string
    {
        return $this->name;
    }
}

然后,如果您实际上想要一个更具体的命名类,则可以像这样继承:

final class UsersTable extends Table
{
    public function __construct()
    {
        parent::__construct('users');
    }
}

如果您使用DI容器并且必须为不同的对象传递不同的表,这将很有用。


3

PHP 7使制作抽象“属性”变得相当容易。就像上面一样,您将通过创建抽象函数来创建它们,但是使用PHP 7可以定义该函数的返回类型,这在构建任何人都可以扩展的基类时使事情变得容易得多。

<?php

abstract class FooBase {

  abstract public function FooProp(): string;
  abstract public function BarProp(): BarClass;

  public function foo() {
    return $this->FooProp();
  }

  public function bar() {
    return $this->BarProp()->name();
  }

}

class BarClass {

  public function name() {
    return 'Bar!';
  }

}

class FooClass extends FooBase {

  public function FooProp(): string {
    return 'Foo!';
  }

  public function BarProp(): BarClass {
    // This would not work:
    // return 'not working';
    // But this will!
    return new BarClass();
  }

}

$test = new FooClass();
echo $test->foo() . PHP_EOL;
echo $test->bar() . PHP_EOL;

1

如果tablename的值在对象的生命周期内永不改变,则以下将是一个简单而安全的实现。

abstract class Foo_Abstract {
    abstract protected function getTablename();

    public function showTableName()
    {
        echo 'my table name is '.$this->getTablename();
    }
}

class Foo extends Foo_Abstract {
    //Foo must 'implement' getTablename()
    protected function getTablename()
    {
        return 'users';
    }
}

这里的关键是指定字符串值'users'并直接在子类实现的getTablename()中返回。该函数模仿“只读”属性。

这与先前发布的使用附加变量的解决方案非常相似。我也喜欢Marco的解决方案,尽管它可能要复杂一些。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.