PHP __get และ __set วิธีการวิเศษ


86

เว้นแต่ฉันจะเข้าใจผิดอย่างสมบูรณ์วิธีการ__getและ__setควรจะอนุญาตให้ใช้→ getและset.

ตัวอย่างเช่นคำสั่งต่อไปนี้ควรเรียกใช้__getเมธอด:

echo $foo->bar;
$var = $foo->bar;

และสิ่งต่อไปนี้ควรใช้__setวิธีการ:

$foo->bar = 'test';

สิ่งนี้ใช้ไม่ได้ในรหัสของฉันและสามารถทำซ้ำได้ด้วยตัวอย่างง่ายๆนี้:

class foo {

    public $bar;
    public function __get($name) {

        echo "Get:$name";
        return $this->$name;
    }

    public function __set($name, $value) {

        echo "Set:$name to $value";
        $this->$name = $value;
    }
}


$foo = new foo();

echo $foo->bar;
$foo->bar = 'test';

echo "[$foo->bar]";

ผลลัพธ์นี้เฉพาะใน:

[test]

การdie()โทรเข้าบ้างแสดงว่าไม่ได้กดเลย

สำหรับตอนนี้ฉันแค่พูดว่าขันสกรูและกำลังใช้งานด้วยตนเองใน__getที่ที่จำเป็นสำหรับตอนนี้ แต่นั่นไม่ได้เป็นแบบไดนามิกมากนักและต้องใช้ความรู้ว่ารหัส 'โอเวอร์โหลด' นั้นไม่ได้ถูกเรียกจริง ๆ เว้นแต่จะมีการเรียกโดยเฉพาะ ฉันต้องการทราบว่าสิ่งนี้ไม่ควรทำงานอย่างที่ฉันเข้าใจว่าควรหรือเหตุใดจึงไม่ได้ผล

php 5.3.3นี้ทำงานอยู่ใน

คำตอบ:


166

__get, __set, __callและ__callStaticจะเรียกเมื่อวิธีการหรือสถานที่ให้บริการจะไม่สามารถเข้าถึง ของคุณ$barเป็นสาธารณะและไม่สามารถเข้าถึงได้

ดูหัวข้อเรื่อง Property Overloading ในคู่มือ:

  • __set() ถูกรันเมื่อเขียนข้อมูลไปยังคุณสมบัติที่ไม่สามารถเข้าถึงได้
  • __get() ใช้สำหรับอ่านข้อมูลจากคุณสมบัติที่ไม่สามารถเข้าถึงได้

วิธีการใช้เวทมนตร์ไม่สามารถทดแทนตัวรับและตัวเซ็ตได้ เพียงแค่อนุญาตให้คุณจัดการการเรียกวิธีการหรือการเข้าถึงคุณสมบัติที่อาจทำให้เกิดข้อผิดพลาด ด้วยเหตุนี้จึงมีหลายสิ่งที่เกี่ยวข้องกับการจัดการข้อผิดพลาด โปรดทราบว่ามันช้ากว่าการเรียกใช้ getter และ setter หรือ direct method อย่างมาก


6
หากต้องการอธิบายอย่างละเอียดเพียงแค่ลบ "แถบ $ สาธารณะ" ออกเพื่อไม่ให้ทรัพย์สินมีอยู่อีกต่อไปและจะใช้งานได้เหมือนมีเสน่ห์
Steffen Müller

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

@airbear มีแพ็คเกจ PECL เก่าโดย Sara Golemon ที่จะช่วยให้คุณสามารถโอเวอร์โหลดโอเปอเรเตอร์ได้ ไม่รู้ว่ามันเข้ากันได้กับ PHP.current อย่างไร
Gordon

1
@Pooya นั่นเป็นเพราะnodeไม่ใช่ทรัพย์สินของ $ foo แต่เป็นทรัพย์สินของdoesNotExist. ดังนั้นเว้นเสียแต่ว่า 'doesNotExist' เป็นวัตถุ (ซึ่งใช้ __set หรือมีคุณสมบัติสาธารณะที่เรียกว่าโหนด) มันจะไม่ทำงาน
Tivie

1
@ อัลเลนใช่มันทำ
Gordon

34

ฉันขอแนะนำให้ใช้อาร์เรย์เพื่อจัดเก็บค่าทั้งหมดผ่านทาง__set().

class foo {

    protected $values = array();

    public function __get( $key )
    {
        return $this->values[ $key ];
    }

    public function __set( $key, $value )
    {
        $this->values[ $key ] = $value;
    }

}

ด้วยวิธีนี้จะทำให้แน่ใจว่าคุณไม่สามารถเข้าถึงตัวแปรด้วยวิธีอื่น (โปรดทราบว่า$valuesได้รับการป้องกัน) เพื่อหลีกเลี่ยงการชนกัน


19

จากคู่มือ PHP :

  • __set () ถูกรันเมื่อเขียนข้อมูลไปยังคุณสมบัติที่ไม่สามารถเข้าถึงได้
  • __get () ใช้สำหรับอ่านข้อมูลจากคุณสมบัติที่ไม่สามารถเข้าถึงได้

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


7

เพื่อขยายคำตอบของ Berry การตั้งค่าระดับการเข้าถึงเป็นแบบป้องกันจะอนุญาตให้ใช้ __get และ __set กับคุณสมบัติที่ประกาศไว้อย่างชัดเจน (อย่างน้อยเมื่อเข้าถึงนอกคลาส) และความเร็วจะช้าลงมากฉันจะเสนอความคิดเห็นจากคำถามอื่น ในหัวข้อนี้และสร้างกรณีสำหรับการใช้งานต่อไป:

ฉันยอมรับว่า __get ช้ากว่าสำหรับฟังก์ชัน get ที่กำหนดเอง (ทำสิ่งเดียวกัน) นี่คือ 0.0124455 เวลาสำหรับ __get () และ 0.0024445 นี้สำหรับ get () ที่กำหนดเองหลังจาก 10,000 ลูป - Melsi 23 พ.ย. 55 เวลา 22:32 น. แนวทางปฏิบัติที่ดีที่สุด: PHP Magic Methods __set และ __get

จากการทดสอบของ Melsi ช้ากว่ามากคือช้ากว่าประมาณ 5 เท่า นั่นช้ากว่ามาก แต่โปรดทราบว่าการทดสอบแสดงให้เห็นว่าคุณยังสามารถเข้าถึงคุณสมบัติได้ด้วยวิธีนี้ 10,000 ครั้งโดยนับเวลาสำหรับการวนซ้ำในเวลาประมาณ 1/100 วินาที มันช้ากว่ามากเมื่อเทียบกับวิธีการรับและกำหนดจริงที่กำหนดไว้และนั่นเป็นการพูดน้อย แต่ในรูปแบบที่ยิ่งใหญ่ของสิ่งต่างๆแม้จะช้ากว่า 5 เท่าก็ไม่เคยช้า

เวลาในการคำนวณของการดำเนินการยังคงน้อยมากและไม่คุ้มค่าที่จะพิจารณาใน 99% ของการใช้งานจริง ครั้งเดียวที่ควรหลีกเลี่ยงจริงๆคือเมื่อคุณกำลังจะเข้าถึงคุณสมบัติมากกว่า 10,000 ครั้งในคำขอเดียว ไซต์ที่มีการเข้าชมสูงกำลังทำสิ่งที่ผิดพลาดหากพวกเขาไม่สามารถทุ่มเซิร์ฟเวอร์เพิ่มอีกสองสามเครื่องเพื่อให้แอปพลิเคชันทำงานต่อไปได้ โฆษณาแบบข้อความบรรทัดเดียวที่ส่วนท้ายของไซต์ที่มีการเข้าชมสูงซึ่งอัตราการเข้าถึงกลายเป็นปัญหาอาจต้องจ่ายค่าฟาร์ม 1,000 เซิร์ฟเวอร์ด้วยข้อความบรรทัดนั้น ผู้ใช้ปลายทางจะไม่แตะนิ้วสงสัยว่าอะไรใช้เวลาโหลดหน้าเว็บนานขนาดนี้เนื่องจากการเข้าถึงคุณสมบัติของแอปพลิเคชันของคุณใช้เวลาหนึ่งในล้านวินาที

ฉันบอกว่าคำพูดนี้ในฐานะนักพัฒนาที่มาจากพื้นหลังใน. NET แต่วิธีการรับและตั้งค่าที่มองไม่เห็นให้กับผู้บริโภคไม่ใช่สิ่งประดิษฐ์ของ. NET พวกเขาไม่ใช่คุณสมบัติหากไม่มีพวกเขาและวิธีการวิเศษเหล่านี้เป็นพระคุณการประหยัดของนักพัฒนา PHP ที่เรียกคุณสมบัติของพวกเขาว่า "คุณสมบัติ" เลย นอกจากนี้ส่วนขยาย Visual Studio สำหรับ PHP ยังรองรับ intellisense พร้อมคุณสมบัติที่ได้รับการป้องกันด้วยเคล็ดลับนั้นฉันคิดว่า ฉันคิดว่ากับนักพัฒนาที่ใช้เมธอดมายากล __get และ __set ด้วยวิธีนี้นักพัฒนา PHP จะปรับเวลาการดำเนินการเพื่อตอบสนองชุมชนนักพัฒนา

แก้ไข: ในทางทฤษฎีคุณสมบัติที่ได้รับการป้องกันดูเหมือนว่าจะใช้ได้ในสถานการณ์ส่วนใหญ่ ในทางปฏิบัติปรากฎว่ามีหลายครั้งที่คุณต้องการใช้ getters และ setters เมื่อเข้าถึงคุณสมบัติภายในนิยามคลาสและคลาสเพิ่มเติม ทางออกที่ดีกว่าคือคลาสพื้นฐานและอินเทอร์เฟซสำหรับการขยายคลาสอื่น ๆ ดังนั้นคุณสามารถคัดลอกโค้ดสองสามบรรทัดจากคลาสพื้นฐานไปยังคลาสที่ใช้งานได้ ฉันกำลังทำเพิ่มอีกเล็กน้อยกับคลาสพื้นฐานของโปรเจ็กต์ดังนั้นฉันจึงไม่มีอินเทอร์เฟซที่จะให้ในตอนนี้ แต่นี่คือนิยามคลาสที่ยังไม่ผ่านการทดสอบพร้อมคุณสมบัติเวทย์มนตร์การรับและการตั้งค่าโดยใช้การสะท้อนเพื่อลบและย้ายคุณสมบัติไปที่ อาร์เรย์ที่ได้รับการป้องกัน:

/** Base class with magic property __get() and __set() support for defined properties. */
class Component {
    /** Gets the properties of the class stored after removing the original
     * definitions to trigger magic __get() and __set() methods when accessed. */
    protected $properties = array();

    /** Provides property get support. Add a case for the property name to
     * expand (no break;) or replace (break;) the default get method. When
     * overriding, call parent::__get($name) first and return if not null,
     * then be sure to check that the property is in the overriding class
     * before doing anything, and to implement the default get routine. */
    public function __get($name) {
        $caller = array_shift(debug_backtrace());
        $max_access = ReflectionProperty::IS_PUBLIC;
        if (is_subclass_of($caller['class'], get_class($this)))
            $max_access = ReflectionProperty::IS_PROTECTED;
        if ($caller['class'] == get_class($this))
            $max_access = ReflectionProperty::IS_PRIVATE;
        if (!empty($this->properties[$name])
            && $this->properties[$name]->class == get_class()
            && $this->properties[$name]->access <= $max_access)
            switch ($name) {
                default:
                    return $this->properties[$name]->value;
            }
    }

    /** Provides property set support. Add a case for the property name to
     * expand (no break;) or replace (break;) the default set method. When
     * overriding, call parent::__set($name, $value) first, then be sure to
     * check that the property is in the overriding class before doing anything,
     * and to implement the default set routine. */
    public function __set($name, $value) {
        $caller = array_shift(debug_backtrace());
        $max_access = ReflectionProperty::IS_PUBLIC;
        if (is_subclass_of($caller['class'], get_class($this)))
            $max_access = ReflectionProperty::IS_PROTECTED;
        if ($caller['class'] == get_class($this))
            $max_access = ReflectionProperty::IS_PRIVATE;
        if (!empty($this->properties[$name])
            && $this->properties[$name]->class == get_class()
            && $this->properties[$name]->access <= $max_access)
            switch ($name) {
                default:
                    $this->properties[$name]->value = $value;
            }
    }

    /** Constructor for the Component. Call first when overriding. */
    function __construct() {
        // Removing and moving properties to $properties property for magic
        // __get() and __set() support.
        $reflected_class = new ReflectionClass($this);
        $properties = array();
        foreach ($reflected_class->getProperties() as $property) {
            if ($property->isStatic()) { continue; }
            $properties[$property->name] = (object)array(
                'name' => $property->name, 'value' => $property->value
                , 'access' => $property->getModifier(), 'class' => get_class($this));
            unset($this->{$property->name}); }
        $this->properties = $properties;
    }
}

ขออภัยหากมีข้อบกพร่องในโค้ด


ฉันพบปัญหาใน 'access' => $ property-> getModifier ()
macki

5

เป็นเพราะ $ bar เป็นทรัพย์สินสาธารณะ

$foo->bar = 'test';

ไม่จำเป็นต้องเรียกใช้เมธอดมายากลเมื่อเรียกใช้ข้างต้น

การลบpublic $bar;ออกจากชั้นเรียนของคุณควรแก้ไขสิ่งนี้


1

วิธีที่ดีที่สุดใช้ Magic set / get ด้วย set / get Methods ที่กำหนดไว้ล่วงหน้าดังตัวอย่างด้านล่าง ด้วยวิธีนี้คุณสามารถรวมสิ่งที่ดีที่สุดของสองโลกเข้าด้วยกัน ในแง่ของความเร็วฉันยอมรับว่ามันช้ากว่าเล็กน้อย แต่คุณรู้สึกได้ถึงความแตกต่าง ตัวอย่างด้านล่างยังตรวจสอบความถูกต้องของอาร์เรย์ข้อมูลกับตัวตั้งค่าที่กำหนดไว้ล่วงหน้า

"วิธีการใช้เวทมนตร์ไม่ได้ใช้แทนตัวรับและตัวเซ็ตได้เพียงแค่ช่วยให้คุณจัดการกับการเรียกใช้เมธอดหรือการเข้าถึงคุณสมบัติที่อาจทำให้เกิดข้อผิดพลาดได้"

นี่คือเหตุผลที่เราควรใช้ทั้งสองอย่าง

ตัวอย่างรายการคลาส

    /*
    * Item class
    */
class Item{
    private $data = array();

    function __construct($options=""){ //set default to none
        $this->setNewDataClass($options); //calling function
    }

    private function setNewDataClass($options){
        foreach ($options as $key => $value) {
            $method = 'set'.ucfirst($key); //capitalize first letter of the key to preserve camel case convention naming
            if(is_callable(array($this, $method))){  //use seters setMethod() to set value for this data[key];      
                $this->$method($value); //execute the setters function
            }else{
                $this->data[$key] = $value; //create new set data[key] = value without seeters;
            }   
        }
    }

    private function setNameOfTheItem($value){ // no filter
        $this->data['name'] = strtoupper($value); //assign the value
        return $this->data['name']; // return the value - optional
    }

    private function setWeight($value){ //use some kind of filter
        if($value >= "100"){ 
            $value = "this item is too heavy - sorry - exceeded weight of maximum 99 kg [setters filter]";
        }
        $this->data['weight'] = strtoupper($value); //asign the value
        return $this->data['weight']; // return the value - optional
    }

    function __set($key, $value){
        $method = 'set'.ucfirst($key); //capitalize first letter of the key to preserv camell case convention naming
        if(is_callable(array($this, $method))){  //use seters setMethod() to set value for this data[key];      
            $this->$method($value); //execute the seeter function
        }else{
            $this->data[$key] = $value; //create new set data[key] = value without seeters;
        }
    }

    function __get($key){
        return $this->data[$key];
    }

    function dump(){
        var_dump($this);
    }
}

INDEX.PHP

$data = array(
    'nameOfTheItem' => 'tv',
    'weight' => '1000',
    'size' => '10x20x30'
);

$item = new Item($data);
$item->dump();

$item->somethingThatDoNotExists = 0; // this key (key, value) will trigger magic function __set() without any control or check of the input,
$item->weight = 99; // this key will trigger predefined setter function of a class - setWeight($value) - value is valid,
$item->dump();

$item->weight = 111; // this key will trigger predefined setter function of a class - setWeight($value) - value invalid - will generate warning.
$item->dump(); // display object info

เอาท์พุท

object(Item)[1]
  private 'data' => 
    array (size=3)
      'name' => string 'TV' (length=2)
      'weight' => string 'THIS ITEM IS TOO HEAVY - SORRY - EXIDED WEIGHT OF MAXIMUM 99 KG [SETTERS FILTER]' (length=80)
      'size' => string '10x20x30' (length=8)
object(Item)[1]
  private 'data' => 
    array (size=4)
      'name' => string 'TV' (length=2)
      'weight' => string '99' (length=2)
      'size' => string '10x20x30' (length=8)
      'somethingThatDoNotExists' => int 0
object(Item)[1]
  private 'data' => 
    array (size=4)
      'name' => string 'TV' (length=2)
      'weight' => string 'THIS ITEM IS TOO HEAVY - SORRY - EXIDED WEIGHT OF MAXIMUM 99 KG [SETTERS FILTER]' (length=80)
      'size' => string '10x20x30' (length=8)
      'somethingThatDoNotExists' => int 0


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