php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #52330 __construct() method should always return a new instance
Submitted: 2010-07-13 16:35 UTC Modified: 2010-07-13 20:25 UTC
From: whistl0r+phpbug at googlemail dot com Assigned:
Status: Wont fix Package: Class/Object related
PHP Version: Irrelevant OS:
Private report: No CVE-ID: None
Have you experienced this issue?
Rate the importance of this bug to you:

 [2010-07-13 16:35 UTC] whistl0r+phpbug at googlemail dot com
Description:
------------
Please see the test script. This should be normal PHP 5.3 class with a good OOP design.

In PHP it is possible, that I can call the constructor multiple times, for example:

$person = new \My\Person('Jens', 'Mander');
echo sprintf('Name: %s %s', $person->getName(), $person->getSurname()); // Will output "Name: Jens Mander"

$person->__construct('John', 'Doe');
echo sprintf('Name: %s %s', $person->getName(), $person->getSurname()); // Will output "Name: John Doe"

In my understanding, it is unexpected, that
1) you can access the constructor method in an instantiated object. The constructor should only instantiate the object - when you have the object, there is no need to call it again. If you need something to "reset" your object state, you should implement such a method.

2) If you can call the constructor again, that it will change the object and *not* return a new instance.

For example:
If you add a "return new \stdClass();" line to your constructor, it will still change the instance you called it from, but now it will also return a "stdClass" - that's inconsistent, isn't it?

Test script:
---------------
<?php
namespace My;

class Person
{
  protected $_name = null;
  
  protected $_surname = null;
  
  /**
  * Constructor.
  * 
  * @param  string OPTIONAL $name
  * @param  string OPTIONAL $surname
  * @return \My\Person
  */
  public function __construct($name = null, $surname = null)
  {
    if ($name !== null)
    {
    	$this->setName($name);
    }
    
    if ($surname !== null)
    {
    	$this->setSurname($surname);
    }
  }
  
  /**
  * Returns the name.
  * 
  * @return null|string Null, when no name was set
  */
  public function getName()
  {
    return $this->_name;
  }
  
  /**
  * Returns the surname.
  * 
  * @return null|string Null, when no name was set
  */
  public function getSurname()
  {
    return $this->_surname;
  }
  
  /**
  * Set the name.
  * 
  * @param  string $name
  * @return \My\Person Provides fluent interface
  * @throws \Exception
  */
  public function setName($name)
  {
    if (!is_string($name) || empty($name))
    {
      throw new \Exception('Name cannot be empty and must be a string!');
    }
    
    $this->_name = $name;
    
    
    return $this;
  }
  
  /**
  * Set the surname.
  * 
  * @param string $name
  * @return \My\Person Provides fluent interface
  * @throws \Exception When $name isn't a string or empty
  */
  public function setSurname($name)
  {
    if (!is_string($name) || empty($name))
    {
      throw new \Exception('Name cannot be empty and must be a string!');
    }
    
    $this->_surname = $name;
    
    
    return $this;
  }
}

Expected result:
----------------
- FATAL error e.g. "Object already constructed!"

- The __construct() call should return a *new* object.

Actual result:
--------------
The __construct() method will work on the object, from where you called it.

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2010-07-13 18:34 UTC] aharvey@php.net
-Status: Open +Status: Wont fix
 [2010-07-13 18:34 UTC] aharvey@php.net
That might seem odd, but it seems consistent enough to me. It breaks down one of two ways:

1. You call the constructor by instantiating an object with new. It behaves like a constructor -- return values are ignored and a new object instance is created.

2. You call the constructor by calling $object->__construct(). It behaves like a method call, including return values being returned.

Basically, if you don't want __construct() to act like a method call, don't call it like a method call.
 [2010-07-13 20:25 UTC] whistl0r+phpbug at googlemail dot com
> Basically, if you don't want __construct() to act like a method call,
> don't call it like a method call.

Well, why does PHP supports different visibilities like "public", "protected" or "private"? You can tell the programmer, "don't read the variable, it's private!" like you say "don't call it like a method". ;)

You support it, because it is part of the OOP concept (encapsulation).

Other languages like Java or C# doesn't allow you to call the constructor like a method. The constructor is only callable with "new" (because it's not a normal method).

Currently, to be safe from OOP view, it seems to me like you must add something like

  protected $_initiated = false

to your class. If this is "true", your constructor method will end. This would make sure, that the constructor will only run once and you object cannot enter an unexpected state, because someone calls the constructor again...
 
PHP Copyright © 2001-2021 The PHP Group
All rights reserved.
Last updated: Sat May 15 03:01:23 2021 UTC