【问题标题】:PHP abstract propertiesPHP 抽象属性
【发布时间】:2011-11-29 21:39:15
【问题描述】:

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

abstract class Foo_Abstract {
    abstract public $tablename;
}

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

【问题讨论】:

    标签: php oop abstract-class


    【解决方案1】:

    没有定义属性这样的事情。

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

    另一方面,一个函数可以被声明(类型、名称、参数)而不被定义(函数体缺失),因此可以被抽象化。

    “Abstract”只表示声明了某些东西但没有定义,因此在使用它之前,你需要定义它,否则它就变得无用了。

    【讨论】:

    • 没有明显的理由不能在 static 属性上使用“抽象”一词 - 但含义略有不同。例如,它可以指示子类必须为属性提供值。
    • 在 TypeScript 中有 abstract properties and accessors。可悲的是,在 php 中这是不可能的。
    【解决方案2】:

    不,没有办法通过编译器强制执行,您必须对 $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;
      }
    }
    

    【讨论】:

    • 不错的功能,我喜欢你实现抽象属性的方式。
    • 这将要求您在抽象基类中将构造函数设为 final。
    • 一些解释:如果你在构造函数内部进行检查并且它应该是强制性的,你需要确保它在每个实例实例化时都被执行。因此,您需要防止它被删除,例如通过扩展类并替换构造函数。 final keyword 你会允许这样做吗?
    • 我喜欢“抽象吸气剂”解决方案。在类抽象中声明函数时,必须将类本身声明为抽象。这意味着除非扩展和完全实现,否则该类是不可用的。扩展该类时,您必须提供“getter”函数的实现。这意味着您还必须在扩展类中创建一个相关属性,因为该函数必须有一些东西要返回。遵循此模式,您将获得与声明抽象属性相同的结果,这也是一种简洁明了的方法。实际上就是这样完成的。
    • 使用抽象 getter 还允许您通过生成一个值来实现它,而不是在有意义的时候返回一个常量值。抽象属性不允许您这样做,尤其是静态属性。
    【解决方案3】:

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

    示例:https://3v4l.org/WH5Xl

    abstract class AbstractFoo
    {
        public $bar;
    
        final public function __construct()
        {
            $this->bar = static::BAR;
        }
    }
    
    class Foo extends AbstractFoo
    {
        //const BAR = 'foobar'; //uncomment to prevent exception
    }
    
    $foo = new Foo(); 
    //Fatal Error: Undefined class constant 'BAR'
    

    但是,如果重新定义,扩展类会覆盖父类的属性和方法。
    例如;如果一个属性在父类中声明为protected,并在扩展类中重新定义为public,则结果属性为public。否则,如果该属性在父级中声明为 private,它将保持为 private,并且对扩展类不可用。

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

    【讨论】:

    • 这里最优雅的解决方案
    【解决方案4】:

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

    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 属性的问题。
    • 我认为抽象方法不能使用私有。
    • @Phate01 据我了解,在评论本身中指出the only "safe" methods to have in a constructor are private and/or final ones,我的解决方法不是这样的情况吗?我在里面使用私人物品
    • 这看起来不错,但它不会强制子类实际设置$name。您可以实现setName() 函数,而无需实际设置$name
    • 我认为使用getName 而不是$name 效果更好。 abstract class Father { abstract protected function getName(); public function foo(){ echo $this->getName();} }
    【解决方案5】:

    对抽象属性的需求可能表明设计存在问题。虽然许多答案都实现了Template method pattern 并且有效,但它看起来总是有点奇怪。

    我们看一下原来的例子:

    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 容器并且必须为不同的对象传递不同的表,这会很有用。

    【讨论】:

      【解决方案6】:

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

      我们想要abstract 属性的原因是确保子类定义它们并在没有定义它们时抛出异常。在我的具体情况下,我需要一些可以与statically 配合使用的东西。

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

      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 getter 中使用它。因此,以下代码有效

      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 以避免某些聪明人(也就是6 个月后的我自己)想要这样做

      class D extends A {
          public static function getProp() {
              // really smart
          }
      }
      
      D::getProp();
      // => no exception...
      

      【讨论】:

      • 这是非常巧妙的 hack。希望将来不需要这样做。
      【解决方案7】:

      你可以通过测试你的代码发现:

      致命错误:属性不能在第 3 行的 ... 中声明为抽象的

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

      但是,您可以实现 getter/setter 函数抽象,这可能就是您要寻找的。​​p>

      没有实现属性(尤其是公共属性),它们只是存在(或不存在):

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

      【讨论】:

        【解决方案8】:

        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;
        

        【讨论】:

          【解决方案9】:

          如果 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';
              }
          }
          

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

          这与之前发布的使用附加变量的解决方案非常相似。我也喜欢 Marco 的解决方案,虽然它可能有点复杂。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2011-08-14
            • 2014-07-12
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2023-03-03
            • 2015-03-30
            相关资源
            最近更新 更多