Nested หรือ Inner Class ใน PHP


111

ฉันกำลังสร้างUser Classสำหรับเว็บไซต์ใหม่ของฉัน แต่คราวนี้ฉันคิดที่จะสร้างมันให้แตกต่างออกไปเล็กน้อย ...

C ++ , Javaและแม้แต่Ruby (และอาจเป็นภาษาการเขียนโปรแกรมอื่น ๆ ) อนุญาตให้ใช้คลาสซ้อน / ชั้นในภายในคลาสหลักซึ่งช่วยให้เราสร้างโค้ดเชิงวัตถุและจัดระเบียบได้มากขึ้น

ใน PHP ฉันต้องการทำสิ่งนี้:

<?php
  public class User {
    public $userid;
    public $username;
    private $password;

    public class UserProfile {
      // some code here
    }

    private class UserHistory {
      // some code here
    }
  }
?>

เป็นไปได้ใน PHP หรือไม่? ฉันจะบรรลุได้อย่างไร?


อัปเดต

หากเป็นไปไม่ได้เวอร์ชัน PHP ในอนาคตอาจรองรับคลาสซ้อนกันหรือไม่


4
สิ่งนี้เป็นไปไม่ได้ใน PHP
Eugene

คุณสามารถขยายUserได้เช่นpublic class UserProfile extends Userและpublic class UserHestory extends User.
Dave Chen

คุณยังสามารถเริ่มต้นด้วยคลาสผู้ใช้นามธรรมจากนั้นขยายได้ php.net/manual/en/language.oop5.abstract.php
Matthew Blancarte

@DaveChen ฉันคุ้นเคยกับการขยายชั้นเรียน แต่ฉันกำลังมองหาโซลูชัน OOP ที่ดีกว่า :( Thx.
Lior Elrom

4
การขยายไม่เหมือนกับการกักกัน ... เมื่อคุณขยายคุณจะได้รับการทำซ้ำคลาส User 3 ครั้ง (ในฐานะผู้ใช้เป็น UserProfile และเป็น UserHistory)
Tomer W

คำตอบ:


137

บทนำ:

คลาสที่ซ้อนกันมีความสัมพันธ์กับคลาสอื่นแตกต่างจากคลาสภายนอกเล็กน้อย การใช้ Java เป็นตัวอย่าง:

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

OuterClass outerObj = new OuterClass(arguments);
outerObj.InnerClass innerObj = outerObj.new InnerClass(arguments);

มีเหตุผลที่น่าสนใจหลายประการในการใช้:

  • เป็นวิธีการจัดกลุ่มคลาสเชิงตรรกะที่ใช้ในที่เดียวเท่านั้น

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

  • มันเพิ่มการห่อหุ้ม

พิจารณาคลาสระดับบนสุด 2 คลาสคือ A และ B โดยที่ B ต้องการเข้าถึงสมาชิกของ A ที่จะประกาศให้เป็นส่วนตัว การซ่อนคลาส B ไว้ภายในคลาส A สมาชิกของ A สามารถประกาศเป็นส่วนตัวและ B สามารถเข้าถึงได้ นอกจากนี้ B เองยังสามารถซ่อนตัวจากโลกภายนอกได้

  • คลาสที่ซ้อนกันสามารถนำไปสู่โค้ดที่อ่านและบำรุงรักษาได้มากขึ้น

คลาสที่ซ้อนกันมักจะเกี่ยวข้องกับคลาสหลักและรวมกันเป็น "แพ็กเกจ"

ใน PHP

คุณสามารถมีพฤติกรรมคล้ายกันใน PHP ได้โดยไม่ต้องมีคลาสซ้อนกัน

หากสิ่งที่คุณต้องการบรรลุคือโครงสร้าง / องค์กรเช่น Package.OuterClass.InnerClass เนมสเปซของ PHP อาจทำได้ดีกว่า คุณยังสามารถประกาศได้มากกว่าหนึ่งเนมสเปซในไฟล์เดียวกัน (แม้ว่าคุณลักษณะการโหลดอัตโนมัติแบบมาตรฐานอาจไม่แนะนำให้ใช้)

namespace;
class OuterClass {}

namespace OuterClass;
class InnerClass {}

หากคุณต้องการเลียนแบบลักษณะอื่น ๆ เช่นการมองเห็นของสมาชิกต้องใช้ความพยายามอีกเล็กน้อย

การกำหนดคลาส "package"

namespace {

    class Package {

        /* protect constructor so that objects can't be instantiated from outside
         * Since all classes inherit from Package class, they can instantiate eachother
         * simulating protected InnerClasses
         */
        protected function __construct() {}

        /* This magic method is called everytime an inaccessible method is called 
         * (either by visibility contrains or it doesn't exist)
         * Here we are simulating shared protected methods across "package" classes
         * This method is inherited by all child classes of Package 
         */
        public function __call($method, $args) {

            //class name
            $class = get_class($this);

            /* we check if a method exists, if not we throw an exception 
             * similar to the default error
             */
            if (method_exists($this, $method)) {

                /* The method exists so now we want to know if the 
                 * caller is a child of our Package class. If not we throw an exception
                 * Note: This is a kind of a dirty way of finding out who's
                 * calling the method by using debug_backtrace and reflection 
                 */
                $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
                if (isset($trace[2])) {
                    $ref = new ReflectionClass($trace[2]['class']);
                    if ($ref->isSubclassOf(__CLASS__)) {
                        return $this->$method($args);
                    }
                }
                throw new \Exception("Call to private method $class::$method()");
            } else {
                throw new \Exception("Call to undefined method $class::$method()");
            }
        }
    }
}

ใช้กรณี

namespace Package {
    class MyParent extends \Package {
        public $publicChild;
        protected $protectedChild;

        public function __construct() {
            //instantiate public child inside parent
            $this->publicChild = new \Package\MyParent\PublicChild();
            //instantiate protected child inside parent
            $this->protectedChild = new \Package\MyParent\ProtectedChild();
        }

        public function test() {
            echo "Call from parent -> ";
            $this->publicChild->protectedMethod();
            $this->protectedChild->protectedMethod();

            echo "<br>Siblings<br>";
            $this->publicChild->callSibling($this->protectedChild);
        }
    }
}

namespace Package\MyParent
{
    class PublicChild extends \Package {
        //Makes the constructor public, hence callable from outside 
        public function __construct() {}
        protected function protectedMethod() {
            echo "I'm ".get_class($this)." protected method<br>";
        }

        protected function callSibling($sibling) {
            echo "Call from " . get_class($this) . " -> ";
            $sibling->protectedMethod();
        }
    }
    class ProtectedChild extends \Package { 
        protected function protectedMethod() {
            echo "I'm ".get_class($this)." protected method<br>";
        }

        protected function callSibling($sibling) {
            echo "Call from " . get_class($this) . " -> ";
            $sibling->protectedMethod();
        }
    }
}

การทดสอบ

$parent = new Package\MyParent();
$parent->test();
$pubChild = new Package\MyParent\PublicChild();//create new public child (possible)
$protChild = new Package\MyParent\ProtectedChild(); //create new protected child (ERROR)

เอาท์พุต:

Call from parent -> I'm Package protected method
I'm Package protected method

Siblings
Call from Package -> I'm Package protected method
Fatal error: Call to protected Package::__construct() from invalid context

บันทึก:

ฉันไม่คิดว่าการพยายามเลียนแบบ innerClasses ใน PHP เป็นความคิดที่ดี ฉันคิดว่าโค้ดไม่ค่อยสะอาดและอ่านง่าย นอกจากนี้อาจมีวิธีอื่นในการบรรลุผลลัพธ์ที่คล้ายคลึงกันโดยใช้รูปแบบที่กำหนดไว้อย่างดีเช่น Observer, Decorator ou COmposition Pattern บางครั้งแม้แต่การถ่ายทอดทางพันธุกรรมก็เพียงพอแล้ว


2
สุดยอดมาก @Tivie! ฉันจะนำโซลูชันนั้นไปใช้กับกรอบงานส่วนขยาย OOP ของฉัน! (ดู github ของฉัน: github.com/SparK-Cruz)
SparK

21

คลาสซ้อนจริงที่มีpublic/ protected/ การprivateเข้าถึงถูกเสนอในปี 2013 สำหรับ PHP 5.6 เป็น RFC แต่ไม่ได้ทำ (ยังไม่มีการลงคะแนนไม่มีการอัปเดตตั้งแต่ปี 2013 - ณ วันที่ 2016/12/29 ):

https://wiki.php.net/rfc/nested_classes

class foo {
    public class bar {
 
    }
}

อย่างน้อยคลาสที่ไม่ระบุชื่อก็ทำให้มันกลายเป็น PHP 7

https://wiki.php.net/rfc/anonymous_classes

จากหน้า RFC นี้:

ขอบเขตในอนาคต

การเปลี่ยนแปลงที่ทำโดยแพตช์นี้หมายถึงคลาสที่ซ้อนกันที่ตั้งชื่อนั้นง่ายต่อการใช้งาน (โดยบิตเล็กน้อย)

ดังนั้นเราอาจได้รับคลาสซ้อนกันในบางเวอร์ชันในอนาคต แต่ยังไม่ได้ตัดสินใจ


12

คุณไม่สามารถทำได้ใน PHP อย่างไรก็ตามมีฟังก์ชันการทำงานวิธีการที่จะทำให้สำเร็จได้

สำหรับรายละเอียดเพิ่มเติมโปรดตรวจสอบโพสต์นี้: วิธีทำ PHP แบบซ้อนคลาสหรือวิธีการซ้อนกัน?

วิธีการใช้งานนี้เรียกว่าอินเทอร์เฟซที่คล่องแคล่ว: http://en.wikipedia.org/wiki/Fluent_interface


ใช่น่าเสียดายที่เป็นวิธีธรรมดา
Lior Elrom

5

ตั้งแต่ PHP เวอร์ชัน 5.4 คุณสามารถบังคับสร้างวัตถุด้วยตัวสร้างส่วนตัวผ่านการสะท้อน สามารถใช้เพื่อจำลองคลาสที่ซ้อนกันของ Java รหัสตัวอย่าง:

class OuterClass {
  private $name;

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

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

  public function forkInnerObject($name) {
    $class = new ReflectionClass('InnerClass');
    $constructor = $class->getConstructor();
    $constructor->setAccessible(true);
    $innerObject = $class->newInstanceWithoutConstructor(); // This method appeared in PHP 5.4
    $constructor->invoke($innerObject, $this, $name);
    return $innerObject;
  }
}

class InnerClass {
  private $parentObject;
  private $name;

  private function __construct(OuterClass $parentObject, $name) {
    $this->parentObject = $parentObject;
    $this->name = $name;
  }

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

  public function getParent() {
    return $this->parentObject;
  }
}

$outerObject = new OuterClass('This is an outer object');
//$innerObject = new InnerClass($outerObject, 'You cannot do it');
$innerObject = $outerObject->forkInnerObject('This is an inner object');
echo $innerObject->getName() . "\n";
echo $innerObject->getParent()->getName() . "\n";

4

ตามความคิดเห็นของ Xenon ต่อคำตอบของAnılÖzselginคลาสที่ไม่ระบุชื่อได้ถูกนำไปใช้ใน PHP 7.0 ซึ่งใกล้เคียงกับคลาสที่ซ้อนกันมากที่สุดเท่าที่คุณจะทำได้ในตอนนี้ นี่คือ RFC ที่เกี่ยวข้อง:

คลาสที่ซ้อนกัน (สถานะ: ถูกถอนออก)

Anonymous Classes (สถานะ: ใช้งานใน PHP 7.0)

ตัวอย่างของโพสต์ต้นฉบับนี่คือลักษณะของโค้ดของคุณ:

<?php
    public class User {
        public $userid;
        public $username;
        private $password;

        public $profile;
        public $history;

        public function __construct() {
            $this->profile = new class {
                // Some code here for user profile
            }

            $this->history = new class {
                // Some code here for user history
            }
        }
    }
?>

แม้ว่าสิ่งนี้จะมาพร้อมกับข้อแม้ที่น่ารังเกียจมาก หากคุณใช้ IDE เช่น PHPStorm หรือ NetBeans แล้วเพิ่มวิธีการเช่นนี้ในUserคลาส:

public function foo() {
  $this->profile->...
}

... ลาก่อนการเติมข้อความอัตโนมัติ ในกรณีนี้แม้ว่าคุณจะเขียนโค้ดไปยังอินเทอร์เฟซ (I ใน SOLID) โดยใช้รูปแบบเช่นนี้:

<?php
    public class User {
        public $profile;

        public function __construct() {
            $this->profile = new class implements UserProfileInterface {
                // Some code here for user profile
            }
        }
    }
?>

เว้นแต่การโทรเพียงครั้งเดียวของคุณ$this->profileจะมาจาก__construct()วิธีการ (หรือวิธีการใด ๆ ที่$this->profileกำหนดไว้) คุณจะไม่ได้รับคำใบ้ประเภทใด ๆ ทรัพย์สินของคุณถูก "ซ่อน" ไว้ใน IDE ของคุณทำให้ชีวิตยากมากหากคุณพึ่งพา IDE ของคุณในการเติมข้อความอัตโนมัติการดมกลิ่นโค้ดและการปรับโครงสร้างใหม่


3

คุณไม่สามารถทำได้ใน PHP PHP รองรับ "include" แต่คุณไม่สามารถทำได้ภายในนิยามคลาส มีตัวเลือกดีๆไม่มากนักที่นี่

นี่ไม่ได้ตอบคำถามของคุณโดยตรง แต่คุณอาจสนใจ "Namespaces" \ syntax \ hacked \ on \ top \ ของ PHP OOP ที่น่าเกลียดมาก: http://www.php.net/manual/en/language .namespaces.rationale.php


Namespaces สามารถจัดระเบียบโค้ดได้ดีกว่า แต่ก็ไม่ได้มีประสิทธิภาพเท่ากับคลาสที่ซ้อนกัน ขอบคุณสำหรับคำตอบ!
Lior Elrom

ทำไมถึงเรียกมันว่า "แย่มาก"? ฉันคิดว่ามันโอเคและแยกออกจากบริบททางไวยากรณ์อื่น ๆ ได้ดี
emfi

2

กำลังรอการโหวตเป็น RFC https://wiki.php.net/rfc/anonymous_classes


1
ฉันไม่เชื่อและคลาสที่ไม่ระบุชื่อจะเสนอฟังก์ชันการทำงานของคลาสที่ซ้อนกัน
Eric G

1
ในเพจ RFC หากคุณค้นหา "ซ้อน" คุณจะเห็นว่ามันรองรับ ไม่เหมือนกันกับวิธี Java แต่รองรับ
AnılÖzselgin

3
ดำเนินการใน PHP 7
Élektra

2

ฉันคิดว่าฉันเขียนวิธีแก้ปัญหานี้อย่างสวยงามโดยใช้เนมสเปซ ในกรณีของฉันคลาสภายในไม่จำเป็นต้องรู้คลาสพาเรนต์ของเขา (เช่นคลาสภายในแบบคงที่ใน Java) ตัวอย่างเช่นฉันสร้างคลาสชื่อ 'ผู้ใช้' และคลาสย่อยที่เรียกว่า 'ประเภท' ซึ่งใช้เป็นข้อมูลอ้างอิงสำหรับประเภทผู้ใช้ (ADMIN, OTHERS) ในตัวอย่างของฉัน ความนับถือ.

User.php (ไฟล์คลาสผู้ใช้)

<?php
namespace
{   
    class User
    {
        private $type;

        public function getType(){ return $this->type;}
        public function setType($type){ $this->type = $type;}
    }
}

namespace User
{
    class Type
    {
        const ADMIN = 0;
        const OTHERS = 1;
    }
}
?>

Using.php (ตัวอย่างวิธีการเรียก 'subclass')

<?php
    require_once("User.php");

    //calling a subclass reference:
    echo "Value of user type Admin: ".User\Type::ADMIN;
?>

2

คุณสามารถทำได้ใน PHP 7:

class User{
  public $id;
  public $name;
  public $password;
  public $Profile;
  public $History;  /*  (optional declaration, if it isn't public)  */
  public function __construct($id,$name,$password){
    $this->id=$id;
    $this->name=$name;
    $this->name=$name;
    $this->Profile=(object)[
        'get'=>function(){
          return 'Name: '.$this->name.''.(($this->History->get)());
        }
      ];
    $this->History=(object)[
        'get'=>function(){
          return ' History: '.(($this->History->track)());
        }
        ,'track'=>function(){
          return (lcg_value()>0.5?'good':'bad');
        }
      ];
  }
}
echo ((new User(0,'Lior','nyh'))->Profile->get)();

-6

ใส่แต่ละคลาสลงในไฟล์แยกกันและ "ต้องการ"

User.php

<?php

    class User {

        public $userid;
        public $username;
        private $password;
        public $profile;
        public $history;            

        public function __construct() {

            require_once('UserProfile.php');
            require_once('UserHistory.php');

            $this->profile = new UserProfile();
            $this->history = new UserHistory();

        }            

    }

?>

UserProfile.php

<?php

    class UserProfile 
    {
        // Some code here
    }

?>

UserHistory.php

<?php

    class UserHistory 
    {
        // Some code here
    }

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