PHP'de İç içe veya İç Sınıf


111

Yeni web sitem için bir Kullanıcı Sınıfı oluşturuyorum, ancak bu sefer onu biraz farklı bir şekilde oluşturmayı düşünüyordum ...

C ++ , Java ve hatta Ruby (ve muhtemelen diğer programlama dilleri), ana sınıf içinde iç içe / iç sınıfların kullanımına izin veriyor, bu da kodu daha nesne odaklı ve düzenli hale getirmemizi sağlıyor.

PHP'de şöyle bir şey yapmak isterim:

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

    public class UserProfile {
      // some code here
    }

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

PHP'de bu mümkün mü? Bunu nasıl başarabilirim?


GÜNCELLEME

İmkansızsa, gelecekteki PHP sürümleri yuvalanmış sınıfları destekleyebilir mi?


4
PHP'de bu imkansız
Eugene

Uzatabilirsiniz User, örneğin: public class UserProfile extends Userve public class UserHestory extends User.
Dave Chen

Ayrıca soyut bir kullanıcı sınıfıyla başlayabilir ve daha sonra genişletebilirsiniz. php.net/manual/en/language.oop5.abstract.php
Matthew Blancarte

@DaveChen sınıfları genişletmeye aşinayım ancak daha iyi bir OOP çözümü arıyorum :( Thx.
Lior Elrom

4
genişletme kapsama ile aynı değildir ... uzattığınızda Kullanıcı sınıfının kopyasını 3 kez alırsınız (Kullanıcı olarak, Kullanıcı Profili olarak ve Kullanıcı Geçmişi olarak)
Tomer W

Yanıtlar:


137

Giriş:

İç içe geçmiş sınıflar, diğer sınıflarla dış sınıflardan biraz farklı şekilde ilişkilidir. Java'yı örnek olarak ele alalım:

Statik olmayan iç içe geçmiş sınıfların, özel olarak bildirilmiş olsalar bile çevreleyen sınıfın diğer üyelerine erişimi vardır. Ayrıca, statik olmayan iç içe geçmiş sınıflar, ana sınıfın bir örneğinin başlatılmasını gerektirir.

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

Bunları kullanmanın birkaç zorlayıcı nedeni vardır:

  • Yalnızca tek bir yerde kullanılan sınıfları mantıksal olarak gruplamanın bir yoludur.

Bir sınıf yalnızca bir başka sınıf için yararlıysa, onu o sınıfta ilişkilendirmek ve gömmek ve ikisini bir arada tutmak mantıklıdır.

  • Kapsüllenmeyi artırır.

B'nin, aksi takdirde özel ilan edilecek olan A'nın üyelerine erişmesi gereken iki üst düzey sınıfı, A ve B'yi düşünün. B sınıfını A sınıfı içinde saklayarak, A'nın üyeleri özel ilan edilebilir ve B bunlara erişebilir. Ek olarak, B'nin kendisi dış dünyadan gizlenebilir.

  • İç içe geçmiş sınıflar, daha okunabilir ve bakımı yapılabilir koda yol açabilir.

İç içe geçmiş bir sınıf, genellikle üst sınıfıyla ilgilidir ve birlikte bir "paket" oluşturur

PHP'de

PHP'de yuvalanmış sınıflar olmadan benzer davranışlarınız olabilir .

Elde etmek istediğiniz tek şey, Package.OuterClass.InnerClass olarak yapı / organizasyon ise, PHP isim alanları yeterli olabilir. Hatta aynı dosyada birden fazla ad alanı bildirebilirsiniz (ancak standart otomatik yükleme özellikleri nedeniyle bu tavsiye edilmeyebilir).

namespace;
class OuterClass {}

namespace OuterClass;
class InnerClass {}

Üye görünürlüğü gibi diğer özellikleri taklit etmek istiyorsanız, biraz daha fazla çaba gerektirir.

"Paket" sınıfını tanımlama

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

Kullanım alanı

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

Test yapmak

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

Çıktı:

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

NOT:

PHP'de innerClasses'ı taklit etmeye çalışmanın gerçekten iyi bir fikir olduğunu düşünmüyorum. Kodun daha az temiz ve okunaklı olduğunu düşünüyorum. Ayrıca, Gözlemci, Dekoratör veya Kompozisyon Modeli gibi iyi kurulmuş bir model kullanarak benzer sonuçlar elde etmenin muhtemelen başka yolları da vardır. Bazen basit bir miras bile yeterlidir.


2
Bu harika @Tivie! Bu çözümü OOP uzantı çerçeveme uygulayacağım! (github'ıma bakın: github.com/SparK-Cruz)
SparK

21

Gerçek iç içe geçmiş sınıflar public/ protected/ privateErişilebilirlik özelliğine 2013'te PHP 5.6 için bir RFC olarak önerildi, ancak bunu yapmadı (Henüz oylama yok, 2013'ten beri güncelleme yok - 2016/12/29 itibarıyla ):

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

class foo {
    public class bar {
 
    }
}

En azından anonim sınıflar PHP 7'ye girdi

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

Bu RFC sayfasından:

Gelecek Kapsam

Bu yama ile yapılan değişiklikler, adlandırılmış iç içe sınıfların uygulanmasının daha kolay olduğu anlamına gelir (biraz da olsa).

Yani, gelecek sürümlerde yuvalanmış sınıflar alabiliriz, ancak henüz karar verilmedi.



5

PHP 5.4 sürümünden beri yansıma yoluyla özel kurucu ile nesneler oluşturmaya zorlayabilirsiniz. İç içe geçmiş Java sınıflarını simüle etmek için kullanılabilir. Örnek kod:

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

Anıl Özselgin'in cevabına Xenon'un yorumuna göre, anonim sınıflar şu anda alacağınız kadar iç içe geçmiş sınıflara yakın olan PHP 7.0'da uygulandı. İlgili RFC'ler şunlardır:

Yuvalanmış Sınıflar (durum: geri çekilmiş)

Anonim Sınıflar (durum: PHP 7.0'da uygulanmıştır)

Orijinal gönderiye bir örnek, kodunuz şöyle görünecektir:

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

Yine de bu, çok kötü bir uyarı ile birlikte geliyor. PHPStorm veya NetBeans gibi bir IDE kullanıyorsanız ve ardından Usersınıfa bunun gibi bir yöntem eklerseniz :

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

... güle güle otomatik tamamlama. Arayüzlere (SOLID'de I) aşağıdaki gibi bir desen kullanarak kodlasanız bile durum budur:

<?php
    public class User {
        public $profile;

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

İçin tek aramalar sürece $this->profilegelmektedir __construct()yöntemi (ya da her türlü yöntem $this->profiletanımlanmıştır) o zaman tip ipuçlarının her türlü almazsınız. Mülkünüz aslında IDE'nizde "gizlidir" ve otomatik tamamlama, kod kokusu koklama ve yeniden düzenleme için IDE'nize güveniyorsanız hayatı çok zorlaştırır.


3

Bunu PHP'de yapamazsınız. PHP "include" yi destekler, ancak bunu bir sınıf tanımının içinde bile yapamazsınız. Burada pek fazla harika seçenek yok.

Bu, sorunuzu doğrudan yanıtlamaz, ancak PHP OOP'nin çok çirkin bir \ sözdizimi \ hacklenmiş \ üzerinde \ top \ "Ad Alanları" ile ilgilenebilirsiniz: http://www.php.net/manual/en/language .namespaces.rationale.php


Ad alanları kesinlikle kodu daha iyi düzenleyebilir ancak iç içe geçmiş sınıflar kadar güçlü değildir. Cevap için teşekkürler!
Lior Elrom

neden "korkunç" diyorsun? bence tamam ve diğer sözdizimi bağlamlarından iyi bir şekilde ayrılmış.
emfi


2

İsim alanlarını kullanarak bu soruna zarif bir çözüm yazdığımı düşünüyorum. Benim durumumda, iç sınıfın kendi ebeveyn sınıfını bilmesine gerek yoktur (Java'daki statik iç sınıf gibi). Örnek olarak, örneğimdeki kullanıcı türleri (ADMIN, OTHERS) için referans olarak kullanılan 'Kullanıcı' adlı bir sınıf ve 'Tür' adlı bir alt sınıf yaptım. Saygılarımızla.

User.php (Kullanıcı sınıfı dosyası)

<?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 ('alt sınıfın' nasıl çağrılacağına dair bir örnek)

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

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

2

PHP 7'de bunun gibi:

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

Her sınıfı ayrı dosyalara koyun ve bunları "zorunlu kılın".

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
    }

?>
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.