Lớp lồng nhau hoặc lớp bên trong trong PHP


111

Tôi đang xây dựng một Lớp người dùng cho trang web mới của mình, tuy nhiên, lần này tôi đã suy nghĩ để xây dựng nó khác đi một chút ...

C ++ , Java và thậm chí cả Ruby (và có thể là các ngôn ngữ lập trình khác) đang cho phép sử dụng các lớp lồng nhau / bên trong lớp chính, cho phép chúng ta làm cho mã hướng đối tượng và có tổ chức hơn.

Trong PHP, tôi muốn làm điều gì đó như sau:

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

    public class UserProfile {
      // some code here
    }

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

Điều đó có thể trong PHP không? Làm thế nào tôi có thể đạt được nó?


CẬP NHẬT

Nếu không thể, các phiên bản PHP trong tương lai có thể hỗ trợ các lớp lồng nhau không?


4
Điều này không thể xảy ra trong PHP
Eugene

Bạn có thể mở rộng nó User, ví dụ: public class UserProfile extends Userpublic class UserHestory extends User.
Dave Chen

Bạn cũng có thể bắt đầu với một lớp người dùng trừu tượng, sau đó mở rộng nó. php.net/manual/en/language.oop5.abstract.php
Matthew Blancarte

@DaveChen tôi quen với việc mở rộng các lớp học tuy nhiên tôi đang tìm kiếm một giải pháp OOP tốt hơn :( Thx.
Lior Elrom

4
mở rộng là không giống như ngăn chặn ... khi bạn mở rộng bạn sẽ có được sự trùng lặp của lớp người dùng 3 lần (như dùng, như UserProfile, và như UserHistory)
Tomer W

Câu trả lời:


136

Giới thiệu:

Các lớp lồng nhau liên quan đến các lớp khác hơi khác so với các lớp bên ngoài. Lấy Java làm ví dụ:

Các lớp lồng nhau không tĩnh có quyền truy cập vào các thành viên khác của lớp bao quanh, ngay cả khi chúng được khai báo là riêng tư. Ngoài ra, các lớp lồng nhau không tĩnh yêu cầu một thể hiện của lớp cha được khởi tạo.

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

Có một số lý do thuyết phục để sử dụng chúng:

  • Đó là một cách nhóm các lớp một cách hợp lý chỉ được sử dụng ở một nơi.

Nếu một lớp chỉ hữu ích cho một lớp khác, thì hợp lý là liên hệ và nhúng nó vào lớp đó và giữ hai lớp lại với nhau.

  • Nó làm tăng khả năng đóng gói.

Hãy xem xét hai lớp cấp cao nhất, A và B, nơi B cần quyền truy cập vào các thành viên của A mà nếu không sẽ được khai báo là private. Bằng cách ẩn lớp B trong lớp A, các thành viên của A có thể được khai báo là riêng tư và B có thể truy cập chúng. Ngoài ra, bản thân B có thể bị che giấu khỏi thế giới bên ngoài.

  • Các lớp lồng nhau có thể dẫn đến mã dễ đọc và dễ bảo trì hơn.

Một lớp lồng nhau thường liên quan đến lớp cha của nó và cùng nhau tạo thành một "gói"

Trong PHP

Bạn có thể có tương tự hành vi trong PHP mà không cần các lớp lồng nhau.

Nếu tất cả những gì bạn muốn đạt được là cấu trúc / tổ chức, như Package.OuterClass.InnerClass, thì không gian tên PHP có thể tốt. Bạn thậm chí có thể khai báo nhiều không gian tên trong cùng một tệp (mặc dù, do các tính năng tự động tải chuẩn, điều đó có thể không được khuyến khích).

namespace;
class OuterClass {}

namespace OuterClass;
class InnerClass {}

Nếu bạn muốn mô phỏng các đặc điểm khác, chẳng hạn như khả năng hiển thị của thành viên, bạn sẽ phải nỗ lực hơn một chút.

Định nghĩa lớp "gói"

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

Ca sử dụng

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

Thử nghiệm

$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)

Đầu ra:

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

GHI CHÚ:

Tôi thực sự không nghĩ rằng cố gắng mô phỏng các lớp bên trong trong PHP là một ý tưởng hay. Tôi nghĩ rằng mã kém sạch sẽ và dễ đọc. Ngoài ra, có lẽ có nhiều cách khác để đạt được kết quả tương tự bằng cách sử dụng một mẫu đã được thiết lập tốt như Người quan sát, Người trang trí hoặc Mẫu bố cục. Đôi khi, chỉ cần thừa kế đơn giản là đủ.


2
Thật tuyệt vời @Tivie! Tôi sẽ triển khai giải pháp đó vào khung mở rộng OOP của mình! (xem github của tôi: github.com/SparK-Cruz)
SparK

21

Các lớp lồng nhau thực có public/ protected/ privatekhả năng truy cập đã được đề xuất vào năm 2013 cho PHP 5.6 dưới dạng RFC nhưng đã không đạt được nó (Chưa có biểu quyết, không có bản cập nhật nào kể từ năm 2013 - tính đến 2016/12/29 ):

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

class foo {
    public class bar {
 
    }
}

Ít nhất, các lớp ẩn danh đã biến nó thành PHP 7

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

Từ trang RFC này:

Phạm vi tương lai

Những thay đổi được thực hiện bởi bản vá này có nghĩa là các lớp lồng nhau được đặt tên dễ thực hiện hơn (một chút).

Vì vậy, chúng ta có thể nhận được các lớp lồng nhau trong một số phiên bản tương lai, nhưng nó vẫn chưa được quyết định.


12

Bạn không thể làm điều này trong PHP. Tuy nhiên, có những cách chức năng để thực hiện điều này.

Để biết thêm chi tiết, vui lòng kiểm tra bài đăng này: Làm thế nào để thực hiện một lớp lồng nhau trong PHP hoặc các phương thức lồng nhau?

Cách thực hiện này được gọi là giao diện thông thạo: http://en.wikipedia.org/wiki/Fluent_interface


Vâng, thật không may, đó là cách thông thường
Lior Elrom

5

Kể từ phiên bản PHP 5.4, bạn có thể buộc tạo các đối tượng với hàm tạo riêng thông qua phản chiếu. Nó có thể được sử dụng để mô phỏng các lớp lồng nhau của Java. Mã ví dụ:

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

Theo nhận xét của Xenon cho câu trả lời của Anıl Özselgin, các lớp ẩn danh đã được triển khai trong PHP 7.0, gần giống với các lớp lồng nhau như bạn sẽ thấy ngay bây giờ. Dưới đây là các RFC có liên quan:

Các lớp lồng nhau (trạng thái: đã rút)

Lớp ẩn danh (trạng thái: được triển khai trong PHP 7.0)

Một ví dụ cho bài đăng gốc, đây là mã của bạn trông như thế nào:

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

Tuy nhiên, điều này đi kèm với một cảnh báo rất khó chịu. Nếu bạn sử dụng một IDE như PHPStorm hoặc NetBeans, sau đó thêm một phương thức như thế này vào Userlớp:

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

... tạm biệt tự động hoàn thành. Đây là trường hợp ngay cả khi bạn viết mã cho các giao diện (I trong SOLID), sử dụng một mẫu như sau:

<?php
    public class User {
        public $profile;

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

Trừ khi các lệnh gọi duy nhất của bạn đến $this->profilelà từ __construct()phương thức (hoặc bất kỳ phương thức nào $this->profileđược định nghĩa) thì bạn sẽ không nhận được bất kỳ loại gợi ý kiểu nào. Thuộc tính của bạn về cơ bản là "ẩn" đối với IDE của bạn, khiến cuộc sống sẽ rất khó khăn nếu bạn dựa vào IDE của mình để tự động hoàn thành, đánh hơi mùi mã và tái cấu trúc.


3

Bạn không thể làm điều đó trong PHP. PHP hỗ trợ "bao gồm", nhưng bạn thậm chí không thể làm điều đó bên trong định nghĩa lớp. Không có nhiều lựa chọn tuyệt vời ở đây.

Điều này không trả lời trực tiếp câu hỏi của bạn, nhưng bạn có thể quan tâm đến "Không gian tên", một cú pháp \ cú pháp \ hacked \ on \ top \ của PHP OOP: http://www.php.net/manual/en/language xấu xí khủng khiếp .namespaces.rationale.php


Không gian tên chắc chắn có thể tổ chức mã tốt hơn nhưng nó không mạnh bằng các lớp lồng nhau. Cảm ơn vì câu trả lời!
Lior Elrom

tại sao bạn gọi nó là "khủng khiếp"? tôi nghĩ rằng nó ổn và được tách biệt khỏi các ngữ cảnh cú pháp khác.
emfi


2

Tôi nghĩ rằng tôi đã viết một giải pháp thanh lịch cho vấn đề này bằng cách sử dụng không gian tên. Trong trường hợp của tôi, lớp bên trong không cần biết lớp cha của mình (giống như lớp bên trong tĩnh trong Java). Như một ví dụ, tôi đã tạo một lớp có tên là 'Người dùng' và một lớp con có tên là 'Loại', được sử dụng làm tham chiếu cho các kiểu người dùng (ADMIN, OTHERS) trong ví dụ của tôi. Trân trọng.

User.php (Tệp lớp người dùng)

<?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 (Ví dụ về cách gọi 'lớp con')

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

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

2

Bạn có thể, như thế này, trong 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

Đặt mỗi lớp thành các tệp riêng biệt và "yêu cầu" chúng.

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
    }

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