PHPのネストされたクラスまたは内部クラス


111

私は建物だユーザークラスをしかし、この時、私は少し違っそれを構築するために考えていた、私の新しいウェブサイトのための...

C ++Java、さらにはRuby(そしておそらく他のプログラミング言語)でも、メインクラス内でネストされたクラスや内部クラスを使用できるため、コードをよりオブジェクト指向で組織化することができます。

PHPでは、私はそのようなことをしたいと思います:

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

    public class UserProfile {
      // some code here
    }

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

PHPでそれは可能ですか?どうすれば達成できますか?


更新

それが不可能な場合、将来のPHPバージョンはネストされたクラスをサポートする可能性がありますか?


4
PHPではこれは不可能
ユージーン

あなたはそれを拡張することができますUser、例:public class UserProfile extends Userpublic class UserHestory extends User
Dave Chen

抽象ユーザークラスから始めて、それを拡張することもできます。php.net/manual/en/language.oop5.abstract.php
マシュー

私は私がよりよいOOPソリューション:( Thxを探していますが、クラスを拡張するに精通してい@DaveChen。
LIOR Elrom

4
拡張は包含と同じではありません...拡張すると、Userクラスの複製が3回取得されます(User、UserProfile、およびUserHistoryとして)
Tomer W

回答:


136

イントロ:

入れ子になったクラスは、外部クラスとは少し異なる方法で他のクラスに関連付けられます。Javaを例にとります:

非静的なネストされたクラスは、それらがプライベートとして宣言されている場合でも、囲んでいるクラスの他のメンバーにアクセスできます。また、静的でないネストされたクラスでは、親クラスのインスタンスをインスタンス化する必要があります。

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

それらを使用する理由はいくつかあります:

  • 1つの場所でのみ使用されるクラスを論理的にグループ化する方法です。

クラスが他の1つのクラスだけに役立つ場合は、そのクラスに関連付けて埋め込み、2つをまとめることが論理的です。

  • カプセル化が向上します。

2つのトップレベルのクラスAとBについて考えます。Bは、そうでなければプライベートとして宣言されるAのメンバーにアクセスする必要があります。クラスA内でクラスBを非表示にすることで、Aのメンバーをプライベートに宣言し、Bがそれらにアクセスできるようになります。さらに、B自体を外部の世界から隠すことができます。

  • ネストされたクラスを使用すると、コードが読みやすく、保守しやすくなります。

ネストされたクラスは通常、その親クラスに関連し、一緒に「パッケージ」を形成します

PHPでは

あなたは同様のものを持つことができますネストされたクラスがなく、PHPで動作を行うます。

Package.OuterClass.InnerClassのように、達成したいすべてが構造/組織である場合、PHP名前空間で十分です。同じファイルで複数の名前空間を宣言することもできます(ただし、標準のオートロード機能のため、お勧めできない場合があります)。

namespace;
class OuterClass {}

namespace OuterClass;
class InnerClass {}

メンバーの可視性などの他の特性をエミュレートしたい場合は、もう少し手間がかかります。

「パッケージ」クラスの定義

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

使用事例

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

テスト中

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

出力:

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

注意:

PHPでinnerClassesをエミュレートしようとすることは、あまり良い考えではないと思います。私はコードがよりクリーンで読みにくいと思います。また、Observer、Decorator、またはCompositionパターンなどの確立されたパターンを使用して同様の結果を達成する他の方法がおそらくあります。単純な継承で十分な場合もあります。


2
@Tivieはすごい!このソリューションをOOP拡張フレームワークに実装するつもりです。(私のgithubを参照してください:github.com/SparK-Cruz)
2013年

21

実入れ子のクラスpublic/ protected/ privateアクセシビリティ(2013年以来、何の更新をまだありません投票- RFCとしてPHP 5.6のために2013年に提案されなかったが、それをしなかった2016年12月29日の時点で):

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

class foo {
    public class bar {
 
    }
}

少なくとも、匿名クラスはPHP 7になりました

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

このRFCページから:

今後のスコープ

このパッチによって行われた変更は、名前付きのネストされたクラスの実装が(ほんの少し)簡単であることを意味します。

したがって、将来のバージョンでネストされたクラスを取得する可能性がありますが、まだ決定されていません。


12

PHPではこれを行うことはできません。ただし、これを実現する機能的な方法があります。

詳細については、この投稿を確認してください: PHPのネストされたクラスまたはネストされたメソッドを実行する方法?

この実装方法は、流暢なインターフェースと呼ばれます。http//en.wikipedia.org/wiki/Fluent_interface


はい、残念ながらこれは従来の方法です
Lior Elrom、2013年

5

PHPバージョン5.4以降、リフレクションを介してプライベートコンストラクターでオブジェクトを強制的に作成できます。Javaのネストされたクラスをシミュレートするために使用できます。コード例:

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の回答に対するXenonのコメントに従って、匿名クラスはPHP 7.0で実装されました。これは、今すぐに取得できるネストされたクラスに近いものです。関連するRFCは次のとおりです。

ネストされたクラス(ステータス:撤回)

匿名クラス(ステータス:PHP 7.0で実装)

元の投稿の例です。コードは次のようになります。

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

ただし、これには非常に厄介な警告が伴います。PHPStormやNetBeansなどのIDEを使用している場合、次のようなメソッドをUserクラスに追加します。

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

...バイバイオートコンプリート。これは、次のようなパターンを使用してインターフェース(SOLIDのI)にコーディングする場合でも当てはまります。

<?php
    public class User {
        public $profile;

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

への唯一の呼び出し$this->profile__construct()メソッド(またはメソッド$this->profileで定義されているもの)からのものでない限り、どのような種類のヒントも得られません。プロパティは基本的にIDEに「隠され」ているため、自動補完、コードのにおいのスニッフィング、およびリファクタリングをIDEに依存している場合、人生は非常に困難になります。


3

PHPではできません。PHPは "include"をサポートしていますが、クラス定義の中でそれを行うこともできません。ここには多くの素晴らしいオプションはありません。

これはあなたの質問に直接答えることはしませんが、PHP OOPのひどく醜い\ syntax \ hacked \ on \ top \の "名前空間"に興味があるかもしれません:http : //www.php.net/manual/en/language .namespaces.rationale.php


名前空間は確かにコードを適切に編成できますが、ネストされたクラスほど強力ではありません。答えてくれてありがとう!
Lior Elrom 2013年

なぜそれを「ひどい」と呼ぶのですか?私はそれは大丈夫で、他の構文コンテキストから十分に分離されていると思います。
emfi

2

RFC https://wiki.php.net/rfc/anonymous_classesとして投票を待ってい ます


1
私は信じていません。匿名クラスはネストされたクラスの機能を提供します。
Eric G

1
RFCページで「ネストされた」を検索すると、サポートされていることがわかります。Javaの方法とまったく同じではありませんが、サポートされています。
アニルÖzselgin

3
PHP 7で実装されました。
Élektra、2015年

2

名前空間を使用して、この問題のエレガントな解決策を書いたと思います。私の場合、内部クラスは(Javaの静的内部クラスのように)親クラスを知る必要はありません。例として、「User」というクラスと「Type」というサブクラスを作成しました。この例では、ユーザータイプ(ADMIN、OTHERS)の参照として使用されています。よろしく。

User.php(ユーザークラスファイル)

<?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(「サブクラス」を呼び出す方法の例)

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

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

2

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

各クラスを別々のファイルに入れて、それらを「要求」します。

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
    }

?>
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.