web-dev-qa-db-ja.com

PHPで複数のコンストラクタを実行するための最良の方法

PHPクラスに、一意の引数シグネチャを持つ2つの__construct関数を配置することはできません。これをやりたいのですが。

class Student 
{
   protected $id;
   protected $name;
   // etc.

   public function __construct($id){
       $this->id = $id;
      // other members are still uninitialized
   }

   public function __construct($row_from_database){
       $this->id = $row_from_database->id;
       $this->name = $row_from_database->name;
       // etc.
   }
}

PHPでこれを行うための最良の方法は何ですか?

319

私はおそらくこのようなことをするでしょう:

<?php

class Student
{
    public function __construct() {
        // allocate your stuff
    }

    public static function withID( $id ) {
        $instance = new self();
        $instance->loadByID( $id );
        return $instance;
    }

    public static function withRow( array $row ) {
        $instance = new self();
        $instance->fill( $row );
        return $instance;
    }

    protected function loadByID( $id ) {
        // do query
        $row = my_awesome_db_access_stuff( $id );
        $this->fill( $row );
    }

    protected function fill( array $row ) {
        // fill all properties from array
    }
}

?>

それから私は私がIDを知っている学生がほしいと思えば:

$student = Student::withID( $id );

または、私はdbの行の配列がある場合:

$student = Student::withRow( $row );

技術的には、複数のコンストラクタを構築するのではなく、静的なヘルパーメソッドを構築するだけですが、このようにしてコンストラクタ内の多くのスパゲッティコードを回避することができます。

433
Kris

クリスの解決策は本当にいいのですが、私は工場と流暢なスタイルの組み合わせがより良いと思います:

<?php

class Student
{

    protected $firstName;
    protected $lastName;
    // etc.

    /**
     * Constructor
     */
    public function __construct() {
        // allocate your stuff
    }

    /**
     * Static constructor / factory
     */
    public static function create() {
        $instance = new self();
        return $instance;
    }

    /**
     * FirstName setter - fluent style
     */
    public function setFirstName( $firstName) {
        $this->firstName = $firstName;
        return $this;
    }

    /**
     * LastName setter - fluent style
     */
    public function setLastName( $lastName) {
        $this->lastName = $lastName;
        return $this;
    }

}

// create instance
$student= Student::create()->setFirstName("John")->setLastName("Doe");

// see result
var_dump($student);
?>
79
timaschew

PHPは動的言語なので、メソッドをオーバーロードすることはできません。あなたはこのようにあなたの議論のタイプをチェックしなければなりません:

class Student 
{
   protected $id;
   protected $name;
   // etc.

   public function __construct($idOrRow){
    if(is_int($idOrRow))
    {
        $this->id = $idOrRow;
        // other members are still uninitialized
    }
    else if(is_array($idOrRow))
    {
       $this->id = $idOrRow->id;
       $this->name = $idOrRow->name;
       // etc.  
    }
}
40
Daff
public function __construct() {
    $parameters = func_get_args();
    ...
}

$o = new MyClass('One', 'Two', 3);

これで、$ parametersは値「One」、「Two」、3の配列になります。

編集、

それを追加できます

func_num_args()

関数にパラメーターの数を与えます。

22
Björn

バージョン5.4以降、PHPは traits をサポートします。これはまさにあなたが探しているものではないものではありませんが、単純なトレイトベースのアプローチは以下のようになります。

trait StudentTrait {
    protected $id;
    protected $name;

    final public function setId($id) {
        $this->id = $id;
        return $this;
    }

    final public function getId() { return $this->id; }

    final public function setName($name) {
        $this->name = $name; 
        return $this;
    }

    final public function getName() { return $this->name; }

}

class Student1 {
    use StudentTrait;

    final public function __construct($id) { $this->setId($id); }
}

class Student2 {
    use StudentTrait;

    final public function __construct($id, $name) { $this->setId($id)->setName($name); }
}

各コンストラクタに1つずつ、合計2つのクラスがありますが、これは少し逆効果です。ある程度の健全性を維持するために、私はファクトリを投入します。

class StudentFactory {
    static public function getStudent($id, $name = null) {
        return 
            is_null($name)
                ? new Student1($id)
                : new Student2($id, $name)
    }
}

だから、それはすべてこれに帰着する:

$student1 = StudentFactory::getStudent(1);
$student2 = StudentFactory::getStudent(1, "yannis");

これは非常に冗長なアプローチですが、非常に便利です。

15
yannis

すでにここで示したように、PHPでmultipleコンストラクタを宣言する方法はたくさんありますが、correctのように宣言する方法はありません(技術的にはPHPでは許可されていないため)。しかし、それによってこの機能がハッキングされるのを防ぐことはできません。これは別の例です。

<?php

class myClass {
    public function __construct() {
        $get_arguments       = func_get_args();
        $number_of_arguments = func_num_args();

        if (method_exists($this, $method_name = '__construct'.$number_of_arguments)) {
            call_user_func_array(array($this, $method_name), $get_arguments);
        }
    }

    public function __construct1($argument1) {
        echo 'constructor with 1 parameter ' . $argument1 . "\n";
    }

    public function __construct2($argument1, $argument2) {
        echo 'constructor with 2 parameter ' . $argument1 . ' ' . $argument2 . "\n";
    }

    public function __construct3($argument1, $argument2, $argument3) {
        echo 'constructor with 3 parameter ' . $argument1 . ' ' . $argument2 . ' ' . $argument3 . "\n";
    }
}

$object1 = new myClass('BUET');
$object2 = new myClass('BUET', 'is');
$object3 = new myClass('BUET', 'is', 'Best.');

出典:複数のコンストラクタを使用し理解するための最も簡単な方法:

お役に立てれば。 :)

15

あなたはこのようなことをすることができます:

public function __construct($param)
{
    if(is_int($param)) {
         $this->id = $param;
    } elseif(is_object($param)) {
     // do something else
    }
 }
13

他の選択肢は、このようにコンストラクタでデフォルトの引数を使うことです。

class Student {

    private $id;
    private $name;
    //...

    public function __construct($id, $row=array()) {
        $this->id = $id;
        foreach($row as $key => $value) $this->$key = $value;
    }
}

これは、$student = new Student($row['id'], $row)のような行でインスタンス化する必要があることを意味しますが、コンストラクタは素晴らしく清潔に保ちます。

一方、多態性を利用したい場合は、そのように2つのクラスを作成できます。

class Student {

    public function __construct($row) {
         foreach($row as $key => $value) $this->$key = $value;
    }
}

class EmptyStudent extends Student {

    public function __construct($id) {
        parent::__construct(array('id' => $id));
    }
}
4
rojoca

他のコメントで述べたように、phpはオーバーロードをサポートしていないので、通常コンストラクタの "型チェックのトリック"は避けられ、ファクトリパターンは代わりに使われます

すなわち。

$myObj = MyClass::factory('fromInteger', $params);
$myObj = MyClass::factory('fromRow', $params);
4
gpilotino

あなたは以下のように本当に簡単でとてもきれいなことをすることができます:

public function __construct()    
{
   $arguments = func_get_args(); 

   switch(sizeof(func_get_args()))      
   {
    case 0: //No arguments
        break; 
    case 1: //One argument
        $this->do_something($arguments[0]); 
        break;              
    case 2:  //Two arguments
        $this->do_something_else($arguments[0], $arguments[1]); 
        break;            
   }
}
4
paishin

この質問はすでにその要求を満たすための非常に賢い方法で答えられてきましたが、なぜ私たちは2つのコンストラクタを持つクラスが必要なのかという根本的な質問をしないでください。私のクラスが2つのコンストラクタを必要とするなら、おそらく私が私のクラスを設計している方法は、よりクリーンでよりテスト可能なデザインを思いつくためにもう少し考慮を必要としません。

クラスをどのようにインスタンス化するかを実際のクラスロジックと混同しています。

Studentオブジェクトが有効な状態にある場合、それがDBの行から作成されたのか、Webフォームまたはcli要求からのデータから作成されたのかは重要ですか。

ここで、db行からオブジェクトを作成するロジックを追加しないのであれば、dbデータからオブジェクトを作成するにはどうすればよいかという疑問に答えるために、別のクラスを追加することができます。データマッパーパターンに慣れている場合、StudentRepositoryを使用できる場合があります。ニーズに合わない場合は、StudentFactoryを使用してあらゆる種類のオブジェクト構築タスクを処理できます。

つまり、ドメインオブジェクトを操作するときには、永続化レイヤを頭から離しておく必要があります。

3
Waku-2

ここに砂粒を追加しましょう

個人的には、クラスのインスタンス(オブジェクト)を返す静的関数としてコンストラクタを追加するのが好きです。次のコードは一例です。

 class Person
 {
     private $name;
     private $email;

     public static function withName($name)
     {
         $person = new Person();
         $person->name = $name;

         return $person;
     }

     public static function withEmail($email)
     {
         $person = new Person();
         $person->email = $email;

         return $person;
     }
 }

これでPersonクラスのインスタンスをこのように作成できることに注意してください。

$person1 = Person::withName('Example');
$person2 = Person::withEmail('yo@mi_email.com');

私はそのコードを以下から取りました:

http://alfonsojimenez.com/post/30377422731/multiple-constructors-in-php

2
Salvi Pascual

これがそれを行うための優雅な方法です。与えられたパラメータ数で複数のコンストラクタを有効にするトレイトを作成します。関数名 "__construct"にパラメータの数を追加するだけです。したがって、1つのパラメータは "__construct1"、2つの "__construct2"などとなります。

trait constructable
{
    public function __construct() 
    { 
        $a = func_get_args(); 
        $i = func_num_args(); 
        if (method_exists($this,$f='__construct'.$i)) { 
            call_user_func_array([($this,$f)],$a); 
        } 
    } 
}

class a{
    use constructable;

    public $result;

    public function __construct1($a){
        $this->result = $a;
    }

    public function __construct2($a, $b){
        $this->result =  $a + $b;
    }
}

echo (new a(1))->result;    // 1
echo (new a(1,2))->result;  // 3
1
Jed Lynch

ここでパーティーに参加するのは非常に遅いと思いますが、かなり柔軟で用途の広い実装を可能にするかなり柔軟なパターンを思いついたのです。

好きな変数を使って、通常どおりクラスを設定します。

class MyClass{
    protected $myVar1;
    protected $myVar2;

    public function __construct($obj = null){
        if($obj){
            foreach (((object)$obj) as $key => $value) {
                if(isset($value) && in_array($key, array_keys(get_object_vars($this)))){
                    $this->$key = $value;
                }
            }
        }
    }
}

オブジェクトを作成するときには、配列のキーを連想配列に渡すだけで、varの名前と同じになります。

$sample_variable = new MyClass([
    'myVar2'=>123, 
    'i_dont_want_this_one'=> 'This won\'t make it into the class'
    ]);

print_r($sample_variable);

このインスタンス化後のprint_r($sample_variable);は次のようになります。

MyClass Object ( [myVar1:protected] => [myVar2:protected] => 123 )

__construct(...)では$groupをnullに初期化しているので、コンストラクタに何も渡さないことも有効です。

$sample_variable = new MyClass();

print_r($sample_variable);

これで、出力は期待どおりになりました。

MyClass Object ( [myVar1:protected] => [myVar2:protected] => )

これを書いたのは、json_decode(...)の出力を直接コンストラクタに渡すことができるようにするためであり、あまり心配する必要はありません。

これはPHP 7.1で実行されました。楽しい!

1
David Culbreth

Php7では、私は同様にパラメータタイプを比較します、あなたは同じ数のパラメータを持つが異なるタイプを持つ二つのコンストラクタを持つことができます。

trait GenericConstructorOverloadTrait
{
    /**
     * @var array Constructors metadata
     */
    private static $constructorsCache;
    /**
     * Generic constructor
     * GenericConstructorOverloadTrait constructor.
     */
    public function __construct()
    {
        $params = func_get_args();
        $numParams = func_num_args();

        $finish = false;

        if(!self::$constructorsCache){
            $class = new \ReflectionClass($this);
            $constructors =  array_filter($class->getMethods(),
                function (\ReflectionMethod $method) {
                return preg_match("/\_\_construct[0-9]+/",$method->getName());
            });
            self::$constructorsCache = $constructors;
        }
        else{
            $constructors = self::$constructorsCache;
        }
        foreach($constructors as $constructor){
            $reflectionParams = $constructor->getParameters();
            if(count($reflectionParams) != $numParams){
                continue;
            }
            $matched = true;
            for($i=0; $i< $numParams; $i++){
                if($reflectionParams[$i]->hasType()){
                    $type = $reflectionParams[$i]->getType()->__toString();
                }
                if(
                    !(
                        !$reflectionParams[$i]->hasType() ||
                        ($reflectionParams[$i]->hasType() &&
                            is_object($params[$i]) &&
                            $params[$i] instanceof $type) ||
                        ($reflectionParams[$i]->hasType() &&
                            $reflectionParams[$i]->getType()->__toString() ==
                            gettype($params[$i]))
                    )
                ) {
                    $matched = false;
                    break;
                }

            }

            if($matched){
                call_user_func_array(array($this,$constructor->getName()),
                    $params);
                $finish = true;
                break;
            }
        }

        unset($constructor);

        if(!$finish){
            throw new \InvalidArgumentException("Cannot match construct by params");
        }
    }

}

使用するには:

class MultiConstructorClass{

    use GenericConstructorOverloadTrait;

    private $param1;

    private $param2;

    private $param3;

    public function __construct1($param1, array $param2)
    {
        $this->param1 = $param1;
        $this->param2 = $param2;
    }

    public function __construct2($param1, array $param2, \DateTime $param3)
    {
        $this->__construct1($param1, $param2);
        $this->param3 = $param3;
    }

    /**
     * @return \DateTime
     */
    public function getParam3()
    {
        return $this->param3;
    }

    /**
     * @return array
     */
    public function getParam2()
    {
        return $this->param2;
    }

    /**
     * @return mixed
     */
    public function getParam1()
    {
        return $this->param1;
    }
}
1
Serginho

うーん、まだこの答えが見えないのに驚いた。リングに帽子を投げるとしよう。

class Action {
    const cancelable    =   0;
    const target        =   1
    const type          =   2;

    public $cancelable;
    public $target;
    public $type;


    __construct( $opt = [] ){

        $this->cancelable   = isset($opt[cancelable]) ? $opt[cancelable] : true;
        $this->target       = isset($opt[target]) ?     $opt[target] : NULL;
        $this->type         = isset($opt[type]) ?       $opt[type] : 'action';

    }
}


$myAction = new Action( [
    Action::cancelable => false,
    Action::type => 'spin',
    .
    .
    .
]);

オプションで、オプションをSplEnumの拡張などの独自のクラスに分離できます。

abstract class ActionOpt extends SplEnum{
    const cancelable    =   0;
    const target        =   1
    const type          =   2;
}
1
Garet Claborn

Krisによるベストアンサー(驚くほど私自身のクラスの設計に役立ちました)への回答として、これが役に立つと思う人たちのための修正版です。任意の列から選択し、配列からオブジェクトデータをダンプするためのメソッドが含まれています。乾杯!

public function __construct() {
    $this -> id = 0;
    //...
}

public static function Exists($id) {
    if (!$id) return false;
    $id = (int)$id;
    if ($id <= 0) return false;
    $mysqli = Mysql::Connect();
    if (mysqli_num_rows(mysqli_query($mysqli, "SELECT id FROM users WHERE id = " . $id)) == 1) return true;
    return false;
}

public static function FromId($id) {
    $u = new self();
    if (!$u -> FillFromColumn("id", $id)) return false;
    return $u;
}

public static function FromColumn($column, $value) {
    $u = new self();
    if (!$u -> FillFromColumn($column, $value)) return false;
    return $u;
}

public static function FromArray($row = array()) {
    if (!is_array($row) || $row == array()) return false;
    $u = new self();
    $u -> FillFromArray($row);
    return $u;
}

protected function FillFromColumn($column, $value) {
    $mysqli = Mysql::Connect();
    //Assuming we're only allowed to specified EXISTENT columns
    $result = mysqli_query($mysqli, "SELECT * FROM users WHERE " . $column . " = '" . $value . "'");
    $count = mysqli_num_rows($result);
    if ($count == 0) return false;
    $row = mysqli_fetch_assoc($result);
    $this -> FillFromArray($row);
}

protected function FillFromArray(array $row) {
    foreach($row as $i => $v) {
        if (isset($this -> $i)) {
            $this -> $i = $v;
        }
    }
}

public function ToArray() {
    $m = array();
    foreach ($this as $i => $v) {
        $m[$i] = $v;    
    }
    return $m;
}

public function Dump() {
    print_r("<PRE>");
    print_r($this -> ToArray());
    print_r("</PRE>");  
}
0
James M

このメソッドは、コンストラクタだけでなくメソッドでも使用できるように作成しました。

私のコンストラクタ:

function __construct() {
    $paramsNumber=func_num_args();
    if($paramsNumber==0){
        //do something
    }else{
        $this->overload('__construct',func_get_args());
    }
}

私のdoSomethingメソッド:

public function doSomething() {
    $paramsNumber=func_num_args();
    if($paramsNumber==0){
        //do something
    }else{
        $this->overload('doSomething',func_get_args());
    }
}

どちらもこの単純な方法で動作します。

public function overloadMethod($methodName,$params){
    $paramsNumber=sizeof($params);
    //methodName1(), methodName2()...
    $methodNameNumber =$methodName.$paramsNumber;
    if (method_exists($this,$methodNameNumber)) {
        call_user_func_array(array($this,$methodNameNumber),$params);
    }
}

だからあなたは宣言することができます

__construct1($arg1), __construct2($arg1,$arg2)...

または

methodName1($arg1), methodName2($arg1,$arg2)...

等々 :)

そして使用時:

$myObject =  new MyClass($arg1, $arg2,..., $argN);

N引数を定義した__constructNを呼び出します。

それから$ myObject - > doSomething($ arg1、$ arg2、...、$ argM)

doSomethingMを定義したMを呼び出します。

0
jechaviz

あなたはいつも何かのような何かという追加のパラメータをコンストラクタに追加し、それからそれに対してswitch文を実行することができます...

class myClass 
{
    var $error ;
    function __construct ( $data, $mode )
    {
        $this->error = false
        switch ( $mode )
        {
            'id' : processId ( $data ) ; break ;
            'row' : processRow ( $data ); break ;
            default : $this->error = true ; break ;
         }
     }

     function processId ( $data ) { /* code */ }
     function processRow ( $data ) { /* code */ }
}

$a = new myClass ( $data, 'id' ) ;
$b = new myClass ( $data, 'row' ) ;
$c = new myClass ( $data, 'something' ) ;

if ( $a->error )
   exit ( 'invalid mode' ) ;
if ( $b->error )
   exit ('invalid mode' ) ;
if ( $c->error )
   exit ('invalid mode' ) ;

また、あなたがより多くの機能性を追加したいならいつでもその方法であなたは単に別のケースをswitchステートメントに追加することができます、そしてまたあなたは誰かが正しいことを送ったことを確かめるためにチェックすることができます。 Cを除いてそれは "何か"に設定されているので、クラス内のエラーフラグが設定され、次に何をするべきかを決定するためにメインプログラムに制御が戻されます。エラーメッセージ "無効なモード" - しかし、代わりに有効なデータが見つかるまでループバックすることもできます。

0
TheKLF99

データ型によってコンストラクタを呼び出す:

class A 
{ 
    function __construct($argument)
    { 
       $type = gettype($argument);

       if($type == 'unknown type')
       {
            // type unknown
       }

       $this->{'__construct_'.$type}($argument);
    } 

    function __construct_boolean($argument) 
    { 
        // do something
    }
    function __construct_integer($argument) 
    { 
        // do something
    }
    function __construct_double($argument) 
    { 
        // do something
    }
    function __construct_string($argument) 
    { 
        // do something
    }
    function __construct_array($argument) 
    { 
        // do something
    }
    function __construct_object($argument) 
    { 
        // do something
    }
    function __construct_resource($argument) 
    { 
        // do something
    }

    // other functions

} 
0
Viral

私が知っている限りでは、オーバーロードはPHPではサポートされていません。 overload()でプロパティのgetメソッドとsetメソッドだけをオーバーロードできます。 ( http://www.php.net/manual/en/overload.examples.basic.php

0
Florian Peschka