web-dev-qa-db-ja.com

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

私は新しいウェブサイト用にser Classを構築していますが、今回は少し違う方法で構築することを考えていました...

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で可能ですか?どうすれば達成できますか?


UPDATE

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

94
Lior Elrom

イントロ:

ネストされたクラスは、外部クラスとは少し異なる方法で他のクラスに関連付けられます。 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でsimilarの動作を使用できます。

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 Patternなどの確立されたパターンを使用して、同様の結果を達成する他の方法もおそらくあります。単純な継承で十分な場合もあります。

123
Tivie

public/protected/privateアクセシビリティを備えた実際のネストされたクラスは、RFCとしてPHP 5.6に対して2013年に提案されましたが、作成されませんでした(投票なし、更新なし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ページから:

将来の範囲

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

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

19

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

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

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

12
Sumoanand

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
Pascal9x

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

これはあなたの質問に直接答えるものではありませんが、ひどくPHPい\ syntax\hacked\on\top\of PHP OOPである「Namespaces」に興味があるかもしれません: http:// www .php.net/manual/en/language.namespaces.rationale.php

3
dkamins

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に依存している場合は非常に困難です。

2
e_i_pi

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

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

sing.php(「サブクラス」の呼び出し方法の例)

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

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

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

1
Anıl Özselgin

このように、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)();
0
Arlon Arriola