Classe imbriquée ou intérieure en PHP

je construis un Classe utilisateur pour mon nouveau site Web, cependant cette fois je pensais à le construire un peu différemment...

je sais que C++ , Java et même Ruby (et probablement d'autres langages de programmation) permet imbriquée/inner classes à l'intérieur de la classe principale qui permet de rendre le code plus d'Objet-Orienté et organisé.

en PHP, I voudrais faire quelque chose comme ça:

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

        public class UserProfile {
            // Some code here
        }

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

est-ce possible en PHP? Comment puis-je l'atteindre?


mise à jour

si c'est impossible, les futures versions de PHP pourront-elles prendre en charge des classes imbriquées?

81
demandé sur Lior Elrom 2013-05-07 20:39:19

9 réponses

Intro:

Les classes imbriquées

se rapportent à d'autres classes un peu différemment des classes externes. Prenons L'exemple de Java:

les classes imbriquées Non statiques ont accès aux autres membres de la classe environnante, même si elles sont déclarées privées. De plus, les classes imbriquées non statiques nécessitent qu'une instance de la classe mère soit instanciée.

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

il y a plusieurs raisons impérieuses de les utiliser:

  • c'est une façon de regrouper logiquement des classes qui ne sont utilisées qu'à un seul endroit.

Si une classe est utile à une seule autre classe, alors il est logique de reliez et intégrez - le dans cette classe et gardez les deux ensemble.

  • il augmente l'encapsulation.

envisager deux classes supérieures, A et B, où B a besoin d'accès de les membres d'Un qui serait par ailleurs déclaré privé. En cachant la classe B dans la classe A, Les membres de A peuvent être déclarés privés et B peut accéder ils. En outre, B lui-même peut être caché du monde extérieur.

  • les classes imbriquées peuvent conduire à un code plus lisible et maintenable.

une classe imbriquée se rapporte habituellement à sa classe mère et forment ensemble un" paquet

en PHP

vous pouvez avoir comportement similaire dans PHP sans classes imbriquées.

si tout ce que vous voulez réaliser est structure/organisation, comme paquet.OuterClass.InnerClass, espaces de noms PHP pourraient sufice. Vous pouvez même déclarer plus d'un namespace dans le même fichier (bien que, en raison des fonctionnalités de chargement automatique standard, cela pourrait ne pas être conseillé).

namespace;
class OuterClass {}

namespace OuterClass;
class InnerClass {}

si vous désirez imiter d'autres caractéristiques, comme la visibilité des membres, cela demande un peu plus d'efforts.

définissant la classe" colis

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

cas d'Utilisation

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

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

sortie:

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

NOTE:

Je ne pense vraiment pas qu'essayer d'imiter les classes en PHP soit une bonne idée. Je pense que le code est moins propre et lisible. En outre, il y a probablement d'autres façons d'obtenir des résultats similaires en utilisant un modèle bien établi tel que le modèle Observer, Decorator ou COmposition. Parfois, même un simple héritage suffit.

112
répondu Tivie 2014-12-01 12:50:44

classes imbriquées avec public / protected / private l'accessibilité a été proposée en 2013 pour PHP 5.6 en tant que RFC mais ne l'a pas fait (pas de vote encore, pas de mise à jour depuis 2013 - en date du 2016/12/29 ):

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

class foo {
    public class bar {

    }
}

au moins, les classes anonymes en PHP 7

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

de cette page RFC:

Avenir

les modifications apportées par ce patch signifient que les classes nommées imbriquées sont plus faciles à implémenter (d'un tout petit peu).

donc, nous pourrions avoir des classes imbriquées dans une future version, mais ce n'est pas encore décidé.

18
répondu Fabian Schmengler 2016-12-29 15:07:48

Vous ne peut pas faites ceci en PHP. Cependant, il existe des fonctionnel des façons d'accomplir ceci.

pour plus de détails, veuillez consulter ce billet: Comment faire une classe PHP imbriquée ou des méthodes imbriquées?

ce mode de mise en œuvre est appelé interface fluide: http://en.wikipedia.org/wiki/Fluent_interface

8
répondu Sumoanand 2017-05-23 11:54:59

vous ne pouvez pas le faire en PHP. PHP supporte "include", mais vous ne pouvez même pas le faire dans une définition de classe. Pas beaucoup de bonnes options ici.

cela ne répond pas directement à votre question, mais vous pourriez être intéressé par "Namespaces", un\syntaxe terriblement laid\hacked\on\top\de PHP OOP: http://www.php.net/manual/en/language.namespaces.rationale.php

3
répondu dkamins 2013-05-07 16:43:56

depuis la version 5.4 de PHP, vous pouvez forcer la création d'objets avec le constructeur privé par la réflexion. Il peut être utilisé pour simuler des classes imbriquées en Java. Exemple de code:

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";
3
répondu Pascal9x 2016-01-04 20:23:21

selon le commentaire de Xenon à la réponse D'Anıl Özselgin, les classes anonymes ont été implémentées dans PHP 7.0, qui est aussi proche des classes imbriquées que vous l'obtiendrez maintenant. Voici les RFC pertinents:

classes imbriquées (statut: retiré)

Anonyme Classes (état: mise en œuvre en PHP 7.0)

un exemple au post original, c'est ce que votre code ressemblerait comme:

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

ceci, cependant, vient avec une mise en garde très désagréable. Si vous utilisez un IDE tel que PHPStorm ou NetBeans, et puis Ajouter une méthode comme celle-ci à la User classe:

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

...bye bye l'auto-complétion. C'est le cas même si vous codez pour des interfaces (le I dans SOLID), en utilisant un modèle comme celui-ci:

<?php
    public class User {
        public $profile;

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

sauf si vos seuls appels à $this->profile sont de la méthode __construct() (ou n'importe quel la méthode $this->profile est définie dans) alors vous n'obtiendrez aucune sorte d'indication de type. Votre propriété est essentiellement "cachée" à votre IDE, ce qui rend la vie très difficile si vous comptez sur votre IDE pour l'auto-completion, l'odeur de code reniflant, et le remaniement.

2
répondu e_i_pi 2017-01-31 23:14:22

Il est en attente pour le droit de vote en tant que RFC https://wiki.php.net/rfc/anonymous_classes

1
répondu Anıl Özselgin 2015-03-19 11:53:24

je pense que j'ai écrit une solution élégante à ce problème en utilisant des espaces de noms. Dans mon cas, la classe interne n'a pas besoin de connaître sa classe mère (comme la classe interne statique en Java). À titre d'exemple, j'ai créé une classe appelée "User" et une sous-classe appelée "Type", utilisée comme référence pour les types d'utilisateurs (ADMIN, autres) dans mon exemple. Égard.

utilisateur.php (fichier de la classe User)

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

à l'Aide.php (An exemple de la façon d'appeler la 'sous-classe')

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

    //calling a subclass reference:
    echo "Value of user type Admin: ".User\Type::ADMIN;
?>
1
répondu Rogerio Souza 2016-08-22 16:25:46

Mettre chaque classe dans des fichiers séparés et "exiger".

de l'Utilisateur.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
    }

?>
-2
répondu priyabagus 2017-11-24 22:59:28