Verschachtelte oder innere Klasse in PHP

111

Ich erstelle eine Benutzerklasse für meine neue Website, aber diesmal habe ich darüber nachgedacht, sie etwas anders zu erstellen ...

C ++ , Java und sogar Ruby (und wahrscheinlich andere Programmiersprachen) ermöglichen die Verwendung verschachtelter / innerer Klassen innerhalb der Hauptklasse, wodurch wir den Code objektorientierter und organisierter gestalten können.

In PHP möchte ich so etwas machen:

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

    public class UserProfile {
      // some code here
    }

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

Ist das in PHP möglich? Wie kann ich das erreichen?


AKTUALISIEREN

Wenn dies nicht möglich ist, werden zukünftige PHP-Versionen möglicherweise verschachtelte Klassen unterstützen?

Lior Elrom
quelle
4
Dies ist unmöglich in PHP
Eugene
Sie könnten es erweitern lassen User, Beispiel: public class UserProfile extends Userund public class UserHestory extends User.
Dave Chen
Sie können auch mit einer abstrakten Benutzerklasse beginnen und diese dann erweitern. php.net/manual/en/language.oop5.abstract.php
Matthew Blancarte
@ DaveChen Ich bin mit dem Erweitern von Klassen vertraut, aber ich suche nach einer besseren OOP-Lösung :( Thx.
Lior Elrom
4
Erweitern ist nicht dasselbe wie Eindämmen ... Wenn Sie erweitern, erhalten Sie dreimal eine Duplizierung der Benutzerklasse (als Benutzer, als Benutzerprofil und als Benutzergeschichte)
Tomer W

Antworten:

136

Intro:

Verschachtelte Klassen beziehen sich etwas anders auf andere Klassen als äußere Klassen. Am Beispiel von Java:

Nicht statisch verschachtelte Klassen haben Zugriff auf andere Mitglieder der einschließenden Klasse, auch wenn sie als privat deklariert sind. Für nicht statisch verschachtelte Klassen muss außerdem eine Instanz der übergeordneten Klasse instanziiert werden.

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

Es gibt mehrere zwingende Gründe für ihre Verwendung:

  • Auf diese Weise können Klassen, die nur an einer Stelle verwendet werden, logisch gruppiert werden.

Wenn eine Klasse nur für eine andere Klasse nützlich ist, ist es logisch, sie in Beziehung zu setzen und in diese Klasse einzubetten und die beiden zusammenzuhalten.

  • Es erhöht die Einkapselung.

Betrachten Sie zwei Klassen der obersten Ebene, A und B, in denen B Zugriff auf Mitglieder von A benötigt, die andernfalls als privat deklariert würden. Durch das Ausblenden von Klasse B in Klasse A können die Mitglieder von A als privat deklariert werden und B kann auf sie zugreifen. Außerdem kann B selbst vor der Außenwelt verborgen werden.

  • Verschachtelte Klassen können zu besser lesbarem und wartbarem Code führen.

Eine verschachtelte Klasse bezieht sich normalerweise auf ihre übergeordnete Klasse und bildet zusammen ein "Paket".

In PHP

Sie können ein ähnliches Verhalten in PHP ohne verschachtelte Klassen haben.

Wenn Sie nur Struktur / Organisation wie Package.OuterClass.InnerClass erreichen möchten, sind PHP-Namespaces möglicherweise ausreichend. Sie können sogar mehr als einen Namespace in derselben Datei deklarieren (obwohl dies aufgrund der Standardfunktionen für das automatische Laden möglicherweise nicht ratsam ist).

namespace;
class OuterClass {}

namespace OuterClass;
class InnerClass {}

Wenn Sie andere Merkmale emulieren möchten, z. B. die Sichtbarkeit von Mitgliedern, ist der Aufwand etwas höher.

Definieren der "Paket" -Klasse

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

Anwendungsfall

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

Testen

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

Ausgabe:

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

HINWEIS:

Ich denke wirklich nicht, dass der Versuch, innerClasses in PHP zu emulieren, eine so gute Idee ist. Ich denke, der Code ist weniger sauber und lesbar. Es gibt wahrscheinlich auch andere Möglichkeiten, ähnliche Ergebnisse mit einem gut etablierten Muster zu erzielen, wie z. B. dem Observer, Decorator oder COmposition Pattern. Manchmal reicht schon eine einfache Vererbung aus.

Tivie
quelle
2
Das ist großartig @Tivie! Ich werde diese Lösung so sehr in mein OOP-Erweiterungsframework implementieren! (siehe mein Github: github.com/SparK-Cruz)
SparK
21

Echte verschachtelte Klassen mit public/ protected/ privateBarrierefreiheit wurden 2013 für PHP 5.6 als RFC vorgeschlagen, haben es aber nicht geschafft (Noch keine Abstimmung, keine Aktualisierung seit 2013 - Stand 29.12.2016 ):

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

class foo {
    public class bar {
 
    }
}

Zumindest haben es anonyme Klassen in PHP 7 geschafft

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

Von dieser RFC-Seite:

Zukünftiger Geltungsbereich

Die durch diesen Patch vorgenommenen Änderungen bedeuten, dass benannte verschachtelte Klassen einfacher zu implementieren sind (um ein kleines bisschen).

Es kann also sein, dass wir in einer zukünftigen Version verschachtelte Klassen erhalten, aber es ist noch nicht entschieden.

Fabian Schmengler
quelle
5

Seit PHP Version 5.4 können Sie das Erstellen von Objekten mit privatem Konstruktor durch Reflektion erzwingen. Es kann verwendet werden, um verschachtelte Java-Klassen zu simulieren. Beispielcode:

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";
Pascal9x
quelle
4

Gemäß Xenons Kommentar zu Anıl Özselgins Antwort wurden anonyme Klassen in PHP 7.0 implementiert, das so nahe an verschachtelten Klassen liegt, wie Sie es derzeit erhalten. Hier sind die relevanten RFCs:

Verschachtelte Klassen (Status: zurückgezogen)

Anonyme Klassen (Status: implementiert in PHP 7.0)

Ein Beispiel für den ursprünglichen Beitrag: So würde Ihr Code aussehen:

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

Dies ist jedoch mit einer sehr bösen Einschränkung verbunden. Wenn Sie eine IDE wie PHPStorm oder NetBeans verwenden und dann der UserKlasse eine Methode wie diese hinzufügen :

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

... bye bye automatische Vervollständigung. Dies ist auch dann der Fall, wenn Sie für Schnittstellen (das I in SOLID) mit einem Muster wie dem folgenden codieren:

<?php
    public class User {
        public $profile;

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

Wenn Ihre einzigen Aufrufe $this->profilenicht von der __construct()Methode stammen (oder von welcher Methode auch immer $this->profile), erhalten Sie keine Typangaben. Ihr Eigentum ist für Ihre IDE im Wesentlichen "verborgen", was das Leben sehr erschwert, wenn Sie sich bei der automatischen Vervollständigung, dem Schnüffeln von Codegerüchen und dem Refactoring auf Ihre IDE verlassen.

e_i_pi
quelle
3

Sie können es nicht in PHP tun. PHP unterstützt "include", aber Sie können dies nicht einmal innerhalb einer Klassendefinition tun. Nicht viele gute Möglichkeiten hier.

Dies beantwortet Ihre Frage nicht direkt, aber Sie könnten an "Namespaces" interessiert sein, einer schrecklich hässlichen \ Syntax \ Hacked \ On \ Top \ von PHP OOP: http://www.php.net/manual/en/language .namespaces.rationale.php

Dkamins
quelle
Namespaces können den Code sicherlich besser organisieren, aber er ist nicht so leistungsfähig wie verschachtelte Klassen. Danke für die Antwort!
Lior Elrom
warum nennst du es "schrecklich"? Ich denke, es ist in Ordnung und gut von anderen Syntaxkontexten getrennt.
EMFI
2

Es wartet auf die Abstimmung als RFC https://wiki.php.net/rfc/anonymous_classes

Anıl Özselgin
quelle
1
Ich glaube nicht, dass eine anonyme Klasse die Funktionalität einer verschachtelten Klasse bietet.
Eric G
1
Wenn Sie auf der RFC-Seite nach "verschachtelt" suchen, sehen Sie, dass diese unterstützt wird. Nicht genau das gleiche wie bei Java, aber es unterstützt.
Anıl Özselgin
3
Implementiert in PHP 7.
Élektra
2

Ich glaube, ich habe eine elegante Lösung für dieses Problem geschrieben, indem ich Namespaces verwendet habe. In meinem Fall muss die innere Klasse ihre übergeordnete Klasse nicht kennen (wie die statische innere Klasse in Java). Als Beispiel habe ich eine Klasse namens 'User' und eine Unterklasse namens 'Type' erstellt, die in meinem Beispiel als Referenz für die Benutzertypen (ADMIN, OTHERS) verwendet werden. Grüße.

User.php (Benutzerklassendatei)

<?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 (Ein Beispiel für den Aufruf der 'Unterklasse')

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

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

Sie können wie folgt in 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)();
Arlon Arriola
quelle
-6

Legen Sie jede Klasse in separate Dateien und "benötigen" Sie sie.

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
    }

?>
Priyabagus
quelle