Kelas Bersarang atau Batin di PHP


111

Saya sedang membangun Kelas Pengguna untuk situs web baru saya, namun kali ini saya berpikir untuk membuatnya sedikit berbeda ...

C ++ , Java dan bahkan Ruby (dan mungkin bahasa pemrograman lain) mengizinkan penggunaan kelas bersarang / dalam di dalam kelas utama, yang memungkinkan kita membuat kode lebih berorientasi objek dan terorganisir.

Di PHP, saya ingin melakukan sesuatu seperti ini:

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

    public class UserProfile {
      // some code here
    }

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

Apakah itu mungkin di PHP? Bagaimana saya bisa mencapainya?


MEMPERBARUI

Jika tidak mungkin, apakah versi PHP masa depan mungkin mendukung kelas bersarang?


4
Ini tidak mungkin di PHP
Eugene

Anda dapat memperpanjangnya User, contoh: public class UserProfile extends Userdan public class UserHestory extends User.
Dave Chen

Anda juga bisa memulai dengan kelas pengguna abstrak, lalu memperluasnya. php.net/manual/en/language.oop5.abstract.php
Matthew Blancarte

@DaveChen Saya terbiasa dengan kelas-kelas tambahan, namun saya mencari solusi OOP yang lebih baik :( Terima kasih
Lior Elrom

4
memperluas tidak sama dengan penahanan ... ketika Anda memperpanjang Anda mendapatkan duplikasi kelas Pengguna 3 kali (sebagai User, sebagai UserProfile, dan sebagai UserHistory)
Tomer W

Jawaban:


136

Intro:

Kelas bertingkat berhubungan dengan kelas lain sedikit berbeda dari kelas luar. Mengambil Java sebagai contoh:

Kelas bertingkat non-statis memiliki akses ke anggota lain dari kelas yang melingkupi, meskipun mereka dideklarasikan sebagai pribadi. Selain itu, kelas bertingkat non-statis memerlukan instance dari kelas induk untuk dibuat.

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

Ada beberapa alasan kuat untuk menggunakannya:

  • Ini adalah cara mengelompokkan kelas secara logis yang hanya digunakan di satu tempat.

Jika sebuah kelas hanya berguna untuk satu kelas lain, maka logis untuk menghubungkan dan menyematkannya di kelas itu dan menjaga keduanya bersama-sama.

  • Ini meningkatkan enkapsulasi.

Pertimbangkan dua kelas tingkat atas, A dan B, di mana B membutuhkan akses ke anggota A yang dinyatakan sebagai pribadi. Dengan menyembunyikan kelas B di dalam kelas A, anggota A dapat dinyatakan sebagai pribadi dan B dapat mengaksesnya. Selain itu, B sendiri bisa disembunyikan dari dunia luar.

  • Kelas bertingkat dapat menghasilkan kode yang lebih mudah dibaca dan dipelihara.

Kelas bersarang biasanya berhubungan dengan kelas induknya dan bersama-sama membentuk "paket"

Di PHP

Anda dapat memiliki perilaku serupa di PHP tanpa kelas bersarang.

Jika semua yang ingin Anda capai adalah struktur / organisasi, seperti Package.OuterClass.InnerClass, namespace PHP mungkin cukup. Anda bahkan dapat mendeklarasikan lebih dari satu namespace dalam file yang sama (meskipun, karena fitur autoloading standar, itu mungkin tidak disarankan).

namespace;
class OuterClass {}

namespace OuterClass;
class InnerClass {}

Jika Anda ingin meniru karakteristik lain, seperti visibilitas anggota, dibutuhkan lebih banyak usaha.

Mendefinisikan kelas "paket"

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

Kasus penggunaan

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

Menguji

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

Keluaran:

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

CATATAN:

Saya benar-benar tidak berpikir mencoba meniru innerClasses di PHP adalah ide yang bagus. Menurut saya kodenya kurang bersih dan mudah dibaca. Selain itu, mungkin ada cara lain untuk mencapai hasil yang serupa dengan menggunakan pola yang sudah mapan seperti Pola Pengamat, Penghias atau Pola Komposisi. Kadang-kadang, warisan sederhana pun sudah cukup.


2
Itu luar biasa @Tivie! Saya akan menerapkan solusi itu ke dalam kerangka ekstensi OOP saya! (lihat github saya: github.com/SparK-Cruz)
SparK

21

Kelas bersarang nyata dengan public/ protected/ privateaksesibilitas diusulkan pada 2013 untuk PHP 5.6 sebagai RFC tetapi tidak berhasil (Belum ada pemungutan suara, tidak ada pembaruan sejak 2013 - pada 2016/12/29 ):

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

class foo {
    public class bar {
 
    }
}

Setidaknya, kelas anonim membuatnya menjadi PHP 7

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

Dari halaman RFC ini:

Lingkup Masa Depan

Perubahan yang dibuat oleh tambalan ini berarti kelas bersarang bernama lebih mudah diimplementasikan (sedikit).

Jadi, kita mungkin mendapatkan kelas bersarang di beberapa versi mendatang, tapi itu belum diputuskan.



5

Sejak PHP versi 5.4 Anda dapat memaksa membuat objek dengan konstruktor pribadi melalui refleksi. Ini dapat digunakan untuk mensimulasikan kelas bertingkat Java. Kode contoh:

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

Sesuai komentar Xenon untuk jawaban Anıl Özselgin, kelas anonim telah diimplementasikan di PHP 7.0, yang sedekat mungkin dengan kelas bersarang seperti yang akan Anda dapatkan sekarang. Berikut adalah RFC yang relevan:

Kelas Bersarang (status: ditarik)

Kelas Anonim (status: diterapkan di PHP 7.0)

Contoh postingan asli, seperti inilah kode Anda akan terlihat:

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

Ini, bagaimanapun, datang dengan peringatan yang sangat buruk. Jika Anda menggunakan IDE seperti PHPStorm atau NetBeans, lalu menambahkan metode seperti ini ke Userkelas:

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

... selamat tinggal penyelesaian otomatis. Ini terjadi bahkan jika Anda membuat kode ke antarmuka (I di SOLID), menggunakan pola seperti ini:

<?php
    public class User {
        public $profile;

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

Kecuali jika satu-satunya panggilan Anda $this->profileberasal dari __construct()metode (atau metode apa pun $this->profileyang didefinisikan) maka Anda tidak akan mendapatkan petunjuk jenis apa pun. Properti Anda pada dasarnya "tersembunyi" ke IDE Anda, membuat hidup menjadi sangat sulit jika Anda mengandalkan IDE Anda untuk pelengkapan otomatis, kode bau, dan pemfaktoran ulang.


3

Anda tidak dapat melakukannya di PHP. PHP mendukung "include", tetapi Anda bahkan tidak dapat melakukannya di dalam definisi kelas. Tidak banyak pilihan bagus di sini.

Ini tidak menjawab pertanyaan Anda secara langsung, tetapi Anda mungkin tertarik dengan "Namespaces", \ syntax \ hacked \ on \ top \ PHP OOP yang sangat jelek: http://www.php.net/manual/en/language .namespaces.rationale.php


Namespaces pasti dapat mengatur kode dengan lebih baik tetapi tidak sekuat kelas bersarang. Terima kasih atas jawabannya!
Lior Elrom

mengapa Anda menyebutnya "mengerikan"? saya pikir tidak apa-apa dan dipisahkan dengan baik dari konteks sintaks lainnya.
emfi


2

Saya rasa saya menulis solusi elegan untuk masalah ini dengan menggunakan ruang nama. Dalam kasus saya, kelas dalam tidak perlu mengetahui kelas induknya (seperti kelas dalam statis di Jawa). Sebagai contoh saya membuat kelas yang disebut 'User' dan subclass yang disebut 'Type', digunakan sebagai referensi untuk jenis pengguna (ADMIN, LAINNYA) dalam contoh saya. Salam.

User.php (File kelas pengguna)

<?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 (Contoh bagaimana memanggil 'subclass')

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

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

2

Anda bisa, seperti ini, di 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

Letakkan setiap kelas ke dalam file terpisah dan "memerlukan" mereka.

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

        }            

    }

?>

ProfilPengguna.php

<?php

    class UserProfile 
    {
        // Some code here
    }

?>

UserHistory.php

<?php

    class UserHistory 
    {
        // Some code here
    }

?>
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.