คุณสมบัตินามธรรมของ PHP


126

มีวิธีใดในการกำหนดคุณสมบัติคลาสนามธรรมใน PHP หรือไม่?

abstract class Foo_Abstract {
    abstract public $tablename;
}

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

คำตอบ:


154

ไม่มีสิ่งที่เรียกว่าการกำหนดคุณสมบัติ

คุณสามารถประกาศคุณสมบัติได้เนื่องจากเป็นที่เก็บข้อมูลที่สงวนไว้ในหน่วยความจำเมื่อเริ่มต้น

ในทางกลับกันสามารถประกาศฟังก์ชัน (ประเภท, ชื่อ, พารามิเตอร์) ได้โดยไม่ต้องกำหนด (ส่วนของฟังก์ชันขาดหายไป) ดังนั้นจึงสามารถทำให้เป็นนามธรรมได้

"บทคัดย่อ" เพียงระบุว่ามีการประกาศบางสิ่งบางอย่าง แต่ไม่ได้กำหนดไว้ดังนั้นก่อนที่จะใช้งานคุณต้องกำหนดไม่เช่นนั้นจะไม่มีประโยชน์


59
ไม่มีเหตุผลที่ชัดเจนที่ไม่สามารถใช้คำว่า 'นามธรรม' กับคุณสมบัติคงที่ได้ - แต่มีความหมายที่แตกต่างกันเล็กน้อย ตัวอย่างเช่นอาจบ่งชี้ว่าคลาสย่อยต้องระบุค่าสำหรับคุณสมบัติ
frodeborli

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เพื่อป้องกันการลบล้าง

คุณสามารถประกาศรับนามธรรมแทน:

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

3
คำอธิบายบางส่วน: หากคุณทำการตรวจสอบภายในตัวสร้างและถ้ามันควรจะบังคับคุณต้องแน่ใจว่ามันได้รับการดำเนินการในทุกอินสแตนซ์อินสแตนซ์ ดังนั้นคุณต้องป้องกันไม่ให้มันถูกลบออกไปเช่นการขยายคลาสและแทนที่คอนสตรัคเตอร์ คำหลักสุดท้ายที่คุณจะอนุญาตให้มีการทำเช่นนั้น
hakre

1
ฉันชอบวิธีแก้ปัญหา "abstract getter" เมื่อคุณประกาศฟังก์ชันในคลาสนามธรรมคุณต้องประกาศคลาสนั้นเป็นนามธรรม ซึ่งหมายความว่าคลาสจะไม่สามารถใช้งานได้เว้นแต่จะขยายและใช้งานได้อย่างสมบูรณ์ เมื่อขยายคลาสนั้นคุณต้องจัดเตรียมการใช้งานสำหรับฟังก์ชัน "getter" ซึ่งหมายความว่าคุณต้องสร้างคุณสมบัติที่เกี่ยวข้องภายในคลาสการขยายเนื่องจากฟังก์ชันต้องมีสิ่งที่ต้องส่งคืน การทำตามรูปแบบนี้คุณจะได้ผลลัพธ์เช่นเดียวกับการประกาศทรัพย์สินที่เป็นนามธรรมนอกจากนี้ยังเป็นแนวทางที่ชัดเจนและชัดเจน นั่นเป็นวิธีที่ทำได้จริง
Salivan

1
การใช้ abstract getter ยังช่วยให้คุณสามารถใช้งานได้โดยการสร้างค่าซึ่งต่างจากการคืนค่าคงที่เมื่อมันสมเหตุสมผล คุณสมบัตินามธรรมไม่อนุญาตให้คุณทำเช่นนั้นโดยเฉพาะคุณสมบัติคงที่
Tobia

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คุณสมบัติ
Robbert

1
ฉันไม่คิดว่าคุณสามารถมีส่วนตัวสำหรับวิธีนามธรรมได้
Zorji

@ Phate01 ตามที่ฉันเข้าใจในความคิดเห็นนั้นระบุว่าthe only "safe" methods to have in a constructor are private and/or final onesไม่ใช่วิธีแก้ปัญหาของฉันในกรณีเช่นนี้? ฉันกำลังใช้
ไพรเวทอยู่

4
นี้หน้าตาดี $nameแต่ก็ไม่ได้บังคับให้ชั้นเด็กที่จะตั้งจริง คุณสามารถใช้ฟังก์ชั่นโดยไม่ได้ตั้งค่าจริงsetName() $name
JohnWE

3
ผมคิดว่าใช้getNameแทนได้$nameผลดีกว่า abstract class Father { abstract protected function getName(); public function foo(){ echo $this->getName();} }
ฮามิด

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จะหลีกเลี่ยงไม่ให้ผู้ชายที่ฉลาดบางคน (หรือใน 6 เดือน) ถูกล่อลวงให้ทำ

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

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

นี่เป็นการแฮ็กที่แยบยลมาก หวังว่าจะไม่จำเป็นต้องทำในอนาคต
CMCDragonkai

6

ดังที่คุณทราบได้จากการทดสอบโค้ดของคุณ:

ข้อผิดพลาดร้ายแรง: ไม่สามารถประกาศคุณสมบัติเป็นนามธรรมใน ... ในบรรทัดที่ 3

ไม่มีไม่มี คุณสมบัติไม่สามารถประกาศนามธรรมใน PHP

อย่างไรก็ตามคุณสามารถใช้นามธรรมของฟังก์ชัน getter / setter ได้นี่อาจเป็นสิ่งที่คุณกำลังมองหา

คุณสมบัติไม่ได้ใช้งาน (โดยเฉพาะคุณสมบัติสาธารณะ) มีอยู่จริง (หรือไม่):

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

6

ความต้องการคุณสมบัติที่เป็นนามธรรมสามารถบ่งบอกถึงปัญหาการออกแบบ ในขณะที่คำตอบจำนวนมากใช้รูปแบบวิธีการของเทมเพลตแต่มันก็ดูแปลก ๆ อยู่เสมอ

ลองดูตัวอย่างต้นฉบับ:

abstract class Foo_Abstract {
    abstract public $tablename;
}

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

การทำเครื่องหมายบางอย่างabstractคือการระบุว่าเป็นสิ่งที่ต้องมี ดีต้องมีค่า (ในกรณีนี้) เป็นพึ่งพาจำเป็นดังนั้นจึงควรถูกส่งผ่านไปสร้างในช่วง instantiation :

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

คีย์ในที่นี้คือค่าสตริง "ผู้ใช้" ถูกระบุและส่งคืนโดยตรงใน getTablename () ในการใช้คลาสย่อย ฟังก์ชันนี้เลียนแบบคุณสมบัติ "อ่านอย่างเดียว"

ค่อนข้างคล้ายกับโซลูชันที่โพสต์ไว้ก่อนหน้านี้ซึ่งใช้ตัวแปรเพิ่มเติม ฉันชอบวิธีแก้ปัญหาของ Marco แม้ว่าจะซับซ้อนกว่าเล็กน้อย

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.