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
마태 복음 Blancarte

@DaveChen 나는 클래스 확장에 익숙하지만 더 나은 OOP 솔루션을 찾고 있습니다 :( Thx.
Lior Elrom

4
확장은 포함과 동일하지 않습니다 ... 확장하면 User 클래스가 3 번 중복됩니다 (User, UserProfile 및 UserHistory)
Tomer W

답변:


136

소개 :

중첩 클래스는 외부 클래스와는 약간 다른 방식으로 다른 클래스와 관련됩니다. Java를 예로 들면 :

비 정적 중첩 클래스는 private로 선언 된 경우에도 바깥 쪽 클래스의 다른 멤버에 액세스 할 수 있습니다. 또한 비 정적 중첩 클래스에는 부모 클래스의 인스턴스를 인스턴스화해야합니다.

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

이를 사용하는 몇 가지 강력한 이유가 있습니다.

  • 한 곳에서만 사용되는 클래스를 논리적으로 그룹화하는 방법입니다.

클래스가 하나의 다른 클래스에만 유용하면 해당 클래스에 연결하고 포함하고 두 클래스를 함께 유지하는 것이 논리적입니다.

  • 캡슐화를 증가시킵니다.

두 개의 최상위 클래스 A와 B를 고려하십시오. 여기서 B는 그렇지 않으면 private으로 선언 될 A의 멤버에 액세스해야합니다. 클래스 A 내에 클래스 B를 숨기면 A의 멤버를 private으로 선언하고 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 ou COmposition Pattern과 같이 잘 확립 된 패턴을 사용하여 유사한 결과를 얻을 수있는 다른 방법이있을 수 있습니다. 때로는 단순한 상속만으로도 충분합니다.


2
@Tivie는 대단합니다! 이 솔루션을 OOP 확장 프레임 워크에 구현할 것입니다! (내 github 참조 : github.com/SparK-Cruz)
SparK 2010 년

21

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 페이지에서 :

미래 범위

이 패치에 의해 변경된 사항은 명명 된 중첩 클래스가 (조금씩) 구현하기 더 쉽다는 것을 의미합니다.

따라서 향후 버전에서 중첩 된 클래스를 얻을 수 있지만 아직 결정되지 않았습니다.



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

... bye bye 자동 완성. 다음과 같은 패턴을 사용하여 인터페이스 (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 \ 인 "Namespaces"에 관심이있을 수 있습니다 : http://www.php.net/manual/en/language .namespaces.rationale.php


네임 스페이스는 확실히 코드를 더 잘 구성 할 수 있지만 중첩 된 클래스만큼 강력하지는 않습니다. 답변 해주셔서 감사합니다!
Lior Elrom

왜 그것을 "끔찍한"이라고 부릅니까? 나는 그것이 괜찮고 다른 구문 컨텍스트와 잘 분리되어 있다고 생각합니다.
emfi


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.