새로운 웹 사이트의 사용자 클래스를 만들고 있습니다만, 이번에는 조금 다른 방법으로 구축하려고 생각하고 있습니다.
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 버전은 중첩된 클래스를 지원할 수 있습니까?
질문에 대한 답변
도입부:
네스트된 클래스는 외부 클래스와는 조금 다르게 다른 클래스와 관련됩니다.Java를 예로 들어 보겠습니다.
비정적 중첩 클래스는 비공개로 선언된 경우에도 엔클로징 클래스의 다른 멤버에 액세스할 수 있습니다.또한 비정적 네스트클래스의 경우 부모 클래스의 인스턴스를 인스턴스화해야 합니다.
OuterClass outerObj = new OuterClass(arguments); outerObj.InnerClass innerObj = outerObj.new InnerClass(arguments);
이러한 기능을 사용하는 데는 다음과 같은 몇 가지 설득력 있는 이유가 있습니다.
- 이는 한 곳에서만 사용되는 클래스를 논리적으로 그룹화하는 방법입니다.
클래스가 다른 하나의 클래스에만 유용한 경우 해당 클래스에 관련지어 포함시키고 두 클래스를 함께 유지하는 것이 논리적입니다.
- 캡슐화가 향상됩니다.
A와 B의 두 가지 최상위 클래스(A와 B)를 고려합니다. 여기서 B는 A의 멤버에 액세스해야 하며, 그렇지 않으면 비공개로 선언될 수 있습니다.클래스 A 내에서 클래스 B를 숨김으로써 A의 멤버를 비공개로 선언하고 B를 액세스 할 수 있습니다.또, B 자체를 외부로부터 숨길 수 있다.
- 클래스가 중첩되면 코드를 더 잘 읽고 유지 관리할 수 있습니다.
네스트된 클래스는 보통 부모 클래스와 관련되며 함께 “패키지”를 형성합니다.
PHP의 경우
중첩된 클래스 없이 PHP에서도 유사한 동작을 할 수 있습니다.
원하는 것이 구조/조직뿐이라면 패키지로.아우터 클래스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 PackageMyParentPublicChild();
//instantiate protected child inside parent
$this->protectedChild = new PackageMyParentProtectedChild();
}
public function test() {
echo "Call from parent -> ";
$this->publicChild->protectedMethod();
$this->protectedChild->protectedMethod();
echo "<br>Siblings<br>";
$this->publicChild->callSibling($this->protectedChild);
}
} }
namespace PackageMyParent {
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 PackageMyParent(); $parent->test(); $pubChild = new PackageMyParentPublicChild();//create new public child (possible) $protChild = new PackageMyParentProtectedChild(); //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 u Coomposition Pattern 등 확립된 패턴을 사용하여 유사한 결과를 얻을 수 있는 다른 방법이 있을 수 있습니다.때로는 단순한 상속으로도 충분합니다.
실제 중첩된 클래스:public
/protected
/private
접근성은 2013년에 PHP 5.6에 대해 RFC로 제안되었지만 실현되지 않았습니다(아직 투표 없음, 2013년 이후 업데이트 없음 – 2021/02/03 현재).
https://wiki.php.net/rfc/nested_classes
class foo {
public class bar {
} }
적어도 익명 클래스는 PHP 7에 포함되었습니다.
https://wiki.php.net/rfc/anonymous_classes
이 RFC 페이지부터:
장래의 범위
네스트 클래스라는 이름의 이 패치 평균에 의해 이루어진 변경은 구현하기 쉬워집니다(조금만).
따라서 향후 버전에서 중첩된 클래스가 제공될 수 있지만 아직 결정되지 않았습니다.
PHP에서는 이 작업을 수행할 수 없습니다.다만, 이것을 실현하는 기능적인 방법이 있습니다.
상세한 것에 대하여는, 다음의 투고를 참조해 주세요.PHP 중첩된 클래스 또는 중첩된 메서드를 수행하는 방법
이 실장 방법을 fluent interface라고 부릅니다.http://en.wikipedia.org/wiki/Fluent_interface
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";
Anyl Ozselgin의 답변에 대한 제논의 코멘트에 따르면 익명의 클래스는 PHP 7.0으로 구현되어 있으며, 이는 지금 바로 얻을 수 있는 네스트된 클래스에 가깝습니다.관련된 RFC는 다음과 같습니다.
원래의 투고의 예로서, 코드는 다음과 같습니다.
<?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()
method(또는 임의의 방법)$this->profile
에 정의되어 있습니다)에서는, 어떠한 타입의 힌트도 얻을 수 없습니다.IDE에 대한 자동 완성, 코드 냄새 스니핑 및 리팩터링을 IDE에 의존하는 경우 자산이 IDE에 대해 “숨겨져” 있기 때문에 생활이 매우 어렵습니다.