Thuộc tính trừu tượng PHP


126

Có cách nào để định nghĩa các thuộc tính lớp trừu tượng trong PHP không?

abstract class Foo_Abstract {
    abstract public $tablename;
}

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

Câu trả lời:


154

Không có những thứ như xác định một tài sản.

Bạn chỉ có thể khai báo các thuộc tính vì chúng là các thùng chứa dữ liệu được lưu trữ trong bộ nhớ khi khởi tạo.

Mặt khác, một hàm có thể được khai báo (loại, tên, tham số) mà không được xác định (thân hàm bị thiếu) và do đó, có thể được làm cho trừu tượng.

"Trừu tượng" chỉ cho biết rằng một cái gì đó đã được khai báo nhưng không được xác định và do đó trước khi sử dụng nó, bạn cần xác định nó hoặc nó trở nên vô dụng.


58
Không có lý do rõ ràng từ 'trừu tượng' không thể được sử dụng trên các thuộc tính tĩnh - nhưng với một ý nghĩa hơi khác. Ví dụ, nó có thể chỉ ra rằng một lớp con phải cung cấp một giá trị cho thuộc tính.
frodeborli

2
Trong TypeScript có các thuộc tính trừu tượng và các bộ truy cập . Thật đáng buồn khi trong php là không thể.
Илья Зеленько

52

Không, không có cách nào để thực thi điều đó với trình biên dịch, bạn phải sử dụng kiểm tra thời gian chạy (giả sử, trong hàm tạo) cho $tablenamebiến, ví dụ:

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

Để thực thi điều này cho tất cả các lớp dẫn xuất của Foo_Ab bát, bạn sẽ phải tạo hàm tạo của Foo_Ab khu vực final, ngăn chặn ghi đè.

Bạn có thể khai báo một getter trừu tượng thay thế:

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

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

Tính năng đẹp, tôi thích cách bạn thực hiện các thuộc tính trừu tượng.
Mathieu Dumoulin

4
Điều này sẽ yêu cầu bạn làm cho hàm tạo cuối cùng trong lớp cơ sở trừu tượng.
hakre

3
Một số giải thích: Nếu bạn thực hiện kiểm tra bên trong hàm tạo và nếu nó là bắt buộc, bạn cần đảm bảo rằng nó được thực thi trên mỗi lần khởi tạo cá thể. Do đó, bạn cần ngăn chặn nó bị xóa, ví dụ bằng cách mở rộng lớp và thay thế hàm tạo. Các từ khóa chính thức bạn sẽ cho phép làm như vậy.
hakre

1
Tôi thích giải pháp "trừu tượng getter". Khi bạn khai báo một hàm trong một lớp trừu tượng, bạn phải khai báo chính lớp đó là trừu tượng. Điều này có nghĩa là lớp không thể sử dụng trừ khi được mở rộng và thực hiện đầy đủ. Khi mở rộng lớp đó, bạn phải cung cấp một triển khai cho hàm "getter". Điều này có nghĩa là bạn cũng phải tạo một thuộc tính có liên quan bên trong lớp mở rộng, bởi vì hàm phải có thứ gì đó để trả về. Theo mô hình này, bạn sẽ nhận được kết quả tương tự như khi bạn khai báo một thuộc tính trừu tượng, đó cũng là một cách tiếp cận rõ ràng và rõ ràng. Đó là cách nó thực sự được thực hiện.
Salivan

1
Sử dụng một getter trừu tượng cũng cho phép bạn thực hiện nó bằng cách tạo ra một giá trị, trái ngược với việc trả về một giá trị không đổi, khi nó có ý nghĩa để làm như vậy. Một thuộc tính trừu tượng sẽ không cho phép bạn làm như vậy, đặc biệt là một thuộc tính tĩnh.
Tobia

27

Tùy thuộc vào ngữ cảnh của thuộc tính nếu tôi muốn buộc khai báo thuộc tính đối tượng trừu tượng trong đối tượng con, tôi muốn sử dụng hằng số với statictừ khóa cho thuộc tính trong phương thức xây dựng đối tượng trừu tượng hoặc phương thức setter / getter. Bạn có thể tùy ý sử dụng finalđể ngăn chặn phương thức bị ghi đè trong các lớp mở rộng.

Ngoài ra, đối tượng con sẽ ghi đè các thuộc tính và phương thức của đối tượng cha nếu được định nghĩa lại. Ví dụ: nếu một thuộc tính được khai báo như protectedtrong cha mẹ và được định nghĩa lại như publicở con, thì kết quả là thuộc tính công khai. Tuy nhiên, nếu tài sản được khai báo privatetrong cha mẹ, nó sẽ vẫn còn privatevà không có sẵn cho đứa trẻ.

http://www.php.net//manual/en/lingu.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
Giải pháp thanh lịch nhất tại đây
Jannie Theunissen

24

Như đã nêu ở trên, không có định nghĩa chính xác như vậy. Tuy nhiên, tôi sử dụng cách giải quyết đơn giản này để buộc lớp con xác định thuộc tính "trừu tượng":

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();
  }
}

Thanh lịch, nhưng không giải quyết vấn đề cho staticcác tài sản.
Robbert

1
Tôi không nghĩ rằng bạn có thể có riêng tư cho các phương pháp trừu tượng.
Zorji

@ Phate01 như tôi hiểu, trong phần bình luận, nó nói the only "safe" methods to have in a constructor are private and/or final ones, không phải là cách giải quyết của tôi như vậy sao? tôi đang sử dụng tư nhân trong đó
ulkas

4
Điều này có vẻ tốt, nhưng nó không buộc một lớp con thực sự phải thiết lập $name. Bạn có thể thực hiện setName()chức năng mà không cần thiết lập $name.
Johnwe

3
Tôi nghĩ rằng sử dụng getNamethay vì $namelàm việc tốt hơn. abstract class Father { abstract protected function getName(); public function foo(){ echo $this->getName();} }
Hamid

7

Tôi đã tự hỏi mình câu hỏi tương tự ngày hôm nay và tôi muốn thêm hai xu của mình.

Lý do chúng tôi muốn abstractcác thuộc tính là để đảm bảo rằng các lớp con định nghĩa chúng và đưa ra các ngoại lệ khi chúng không có. Trong trường hợp cụ thể của tôi, tôi cần một cái gì đó có thể làm việc với staticđồng minh.

Lý tưởng nhất là tôi muốn một cái gì đó như thế này:

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.
}

Tôi đã kết thúc với việc thực hiện này

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
{
}

Như bạn có thể thấy, trong Atôi không định nghĩa $prop, nhưng tôi sử dụng nó trong một staticgetter. Do đó, đoạn mã sau hoạt động

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

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

Trong CMặt khác, tôi không xác định $prop, vì vậy tôi có được ngoại lệ:

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

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

Tôi phải gọi getProp()  phương thức để có ngoại lệ và tôi không thể tải nó khi tải lớp, nhưng nó khá gần với hành vi mong muốn, ít nhất là trong trường hợp của tôi.

Tôi xác định getProp()finalđể tránh việc một người thông minh nào đó (còn gọi là bản thân tôi trong 6 tháng) bị cám dỗ để làm

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

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

Đây là hack rất khéo léo. Hy vọng rằng điều này không cần phải được thực hiện trong tương lai.
CMCDragonkai

6

Như bạn có thể đã tìm ra bằng cách chỉ kiểm tra mã của bạn:

Lỗi nghiêm trọng: Thuộc tính không thể được khai báo trừu tượng trong ... trên dòng 3

Không có. Các thuộc tính không thể được khai báo trừu tượng trong PHP.

Tuy nhiên, bạn có thể thực hiện một tóm tắt chức năng getter / setter, đây có thể là những gì bạn đang tìm kiếm.

Các thuộc tính không được triển khai (đặc biệt là các thuộc tính công cộng), chúng chỉ tồn tại (hoặc không):

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

6

Sự cần thiết cho các thuộc tính trừu tượng có thể chỉ ra các vấn đề thiết kế. Trong khi nhiều câu trả lời triển khai loại mẫu phương thức Mẫu và nó hoạt động, nó luôn trông có vẻ lạ.

Hãy xem ví dụ ban đầu:

abstract class Foo_Abstract {
    abstract public $tablename;
}

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

Để đánh dấu một cái gì đó abstractlà chỉ ra nó là một thứ bắt buộc phải có. Chà, một giá trị bắt buộc (trong trường hợp này) là một phụ thuộc bắt buộc, vì vậy nó phải được chuyển đến hàm tạo trong quá trình khởi tạo :

class Table
{
    private $name;

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

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

Sau đó, nếu bạn thực sự muốn một lớp có tên cụ thể hơn, bạn có thể kế thừa như vậy:

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

Điều này có thể hữu ích nếu bạn sử dụng DI container và phải chuyển các bảng khác nhau cho các đối tượng khác nhau.


3

PHP 7 làm cho nó dễ dàng hơn một chút để tạo các "thuộc tính" trừu tượng. Cũng như trên, bạn sẽ tạo chúng bằng cách tạo các hàm trừu tượng, nhưng với PHP 7, bạn có thể định nghĩa kiểu trả về cho hàm đó, điều này giúp mọi thứ dễ dàng hơn rất nhiều khi bạn xây dựng một lớp cơ sở mà bất kỳ ai cũng có thể mở rộng.

<?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

nếu giá trị tablename sẽ không bao giờ thay đổi trong suốt vòng đời của đối tượng, sau đây sẽ là một triển khai đơn giản nhưng an toàn.

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

chìa khóa ở đây là giá trị chuỗi 'người dùng' được chỉ định và được trả về trực tiếp trong getTablename () khi triển khai lớp con. Hàm bắt chước một thuộc tính "chỉ đọc".

Điều này khá giống với một giải pháp được đăng trước đó sử dụng một biến bổ sung. Tôi cũng thích giải pháp của Marco mặc dù nó có thể phức tạp hơn một chút.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.