web-dev-qa-db-fra.com

Classe imbriquée ou interne dans PHP

Je construis un User Class pour mon nouveau site web, mais 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) autorise des classes imbriquées/internes dans la classe principale, ce qui permet de rendre le code plus orienté et organisé.

En PHP, je voudrais faire quelque chose comme ceci:

<?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 y arriver?


METTRE &AGRAVE; JOUR

Si c'est impossible, les versions futures PHP pourront-elles gérer les classes imbriquées?

87
Lior Elrom

Intro:

Les classes imbriquées se rapportent aux autres classes un peu différemment des classes externes. En prenant Java comme exemple:

Les classes imbriquées non statiques ont accès aux autres membres de la classe englobante, même si elles sont déclarées privées. De plus, les classes imbriquées non statiques nécessitent l'instanciation d'une instance de la classe parente.

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 n'est utile qu'à une autre classe, il est logique de relier et intégrer dans cette classe et garder les deux ensemble.

  • Cela augmente l'encapsulation.

Prenons deux classes de niveau supérieur, A et B, où B doit avoir accès à les membres de A qui seraient autrement déclarés privés. En cachant la classe B dans la classe A, les membres de A peuvent être déclarés privés et B peut y accéder leur. De plus, 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 généralement à sa classe parente et forme un "paquet"

En PHP

Vous pouvez avoir similar behavior dans PHP sans classes imbriquées. 

Si tout ce que vous voulez réaliser est une structure/organisation, en tant que Package.OuterClass.InnerClass, les espaces de noms PHP peuvent vous suffire. Vous pouvez même déclarer plusieurs espaces de noms dans le même fichier (bien que, en raison des fonctionnalités standard de chargement automatique, il se peut que cela ne soit pas conseillé).

namespace;
class OuterClass {}

namespace OuterClass;
class InnerClass {}

Si vous souhaitez émuler d'autres caractéristiques, telles que la visibilité des membres, un peu plus d'effort est nécessaire.

Définir la classe "package"

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

Essai

$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

REMARQUE:

Je ne pense vraiment pas que d'essayer d'imiter innerClasses dans PHP soit une bonne idée. Je pense que le code est moins propre et lisible. En outre, il existe probablement d'autres moyens d'obtenir des résultats similaires en utilisant un modèle bien établi tel que le modèle Observer, Decorator ou COmposition. Parfois, un simple héritage suffit.

121
Tivie

Des classes imbriquées réelles avec publicprotected/private accessibilité ont été proposées en 2013 pour PHP 5.6 en tant que RFC, mais ne l'ont pas faite (pas encore de vote, pas de mise à jour depuis 2013 - _/à partir de 2016/12/29 ):

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

class foo {
    public class bar {

    }
}

Au moins, les classes anonymes en ont fait PHP 7

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

De cette page RFC:

Portée future

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

Donc, nous pourrions obtenir des classes imbriquées dans une version future, mais cela n’a pas encore été décidé.

19
Fabian Schmengler

Vous ne pouvez pas faire cela en PHP. Cependant, il existe des moyens fonctionnels pour y parvenir.

Pour plus de détails s'il vous plaît vérifier ce post: Comment faire une classe imbriquée PHP ou des méthodes imbriquées?

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

12
Sumoanand

Depuis PHP version 5.4, vous pouvez forcer la création d'objets avec un constructeur privé par réflexion. Il peut être utilisé pour simuler des classes imbriquées 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";
4
Pascal9x

Vous ne pouvez pas le faire en PHP. PHP prend en charge "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", une syntaxe terriblement laide\hackée sur\top\de PHP OOP: http: // www. php.net/manual/en/language.namespaces.rationale.php

3
dkamins

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

Classes imbriquées (statut: retiré)

Classes anonymes (statut: implémenté dans PHP 7.0)

Un exemple pour le post original, voici à quoi ressemblerait votre code:

<?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 méchante. Si vous utilisez un IDE tel que PHPStorm ou NetBeans, puis ajoutez une méthode de ce type à la classe User:

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

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

À moins que vos seuls appels à $this->profile proviennent de la méthode __construct() (ou quelle que soit la méthode définie par $this->profile dans), vous n'obtiendrez aucun indice de type. Votre propriété est essentiellement "cachée" pour votre IDE, rendant la vie très difficile si vous comptez sur votre IDE pour la complétion automatique, le reniflement des odeurs de code et le refactoring.

2
e_i_pi

Je pense avoir é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 parente (comme la classe interne statique en Java). A titre d'exemple, j'ai créé une classe appelée "User" et une sous-classe appelée "Type", utilisées comme référence pour les types d'utilisateurs (ADMIN, OTHERS) dans mon exemple. Cordialement.

User.php (fichier de classe utilisateur)

<?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 (Un exemple d'appeler la 'sous-classe')

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

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

Il attend le vote en tant que RFC https://wiki.php.net/rfc/anonymous_classes

1
Anıl Özselgin

Vous pouvez, comme ceci:

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)();
0
Arlon Arriola