【问题标题】:Returning a value in constructor function of a class在类的构造函数中返回值
【发布时间】:2011-10-14 13:00:25
【问题描述】:

到目前为止,我有一个带有构造函数的 PHP

public function __construct ($identifier = NULL)
{
 // Return me.
if ( $identifier != NULL )
{
  $this->emailAddress = $identifier;
  if ($this->loadUser() )
    return $this;      
  else
  {
// registered user requested , but not found ! 
return false;
  }
}

loadUser 的功能是在数据库中查找特定的电子邮件地址。 当我将标识符设置为某些电子邮件时,我确定它不在数据库中;第一个 IF 被通过,然后进入第一个 ELSE。这里构造函数应该返回 FALSE;但相反,它返回一个具有所有 NULL 值的类的对象!

如何防止这种情况发生?谢谢

编辑:

谢谢大家的回答。那是相当快的!我看到 OOP 方法是抛出异常。所以抛出一个,我的问题改变了我应该如何处理异常? php.net 的手册相当混乱!

    // Setup the user ( we assume he is a user first. referees, admins are   considered users too )
    try { $him = new user ($_emailAddress);
    } catch (Exception $e_u) { 
      // try the groups database
      try { $him = new group ($_emailAddress); 
      } catch (Exception $e_g) {
          // email address was not in any of them !!  
        }
    }

【问题讨论】:

  • 构造函数不应显式返回任何内容。如果您需要发出失败信号,则抛出调用代码可以捕获的异常。
  • 你应该写一个答案 Marc B,我会投票给你。
  • 投票和接受最多的答案远非最佳答案。请考虑更改接受的答案。

标签: php oop class constructor


【解决方案1】:

构造函数只能返回它试图创建的对象。如果实例化没有正确完成,您将得到一个充满 NULL 属性的类实例,正如您所发现的那样。

如果对象以不完整或错误状态加载,我建议设置一个属性来指示。

// error status property
public $error = NULL;

public function __construct ($identifier = NULL)
{
 // Return me.
if ( $identifier != NULL )
{
  $this->emailAddress = $identifier;
  if (!$this->loadUser() )
  {
   // registered user requested , but not found ! 
   $this->error = "user not found";
  }
}

然后在实例化对象时,可以检查它是否有错误状态:

$obj = new MyObject($identifier);
if (!empty($obj->error)) {
   // something failed.
}

另一种(也许更好)替代方法是在构造函数中抛出异常,并将实例化包装在 try/catch 中。

【讨论】:

  • 这不好,对象应该代表一个用户。你所做的是一个代表用户“承诺”的对象,它可能会失败,然后你必须每次都检查错误。否则,您将面临与实际上不是用户的用户合作的风险。例外也不好。因为通过 id 查找用户但没有找到它并没有什么特别的,所以它一直在发生。仅当您对堆栈跟踪感兴趣时才使用异常!只有当您的代码被破坏并需要修复时,这种情况才会或多或少发生。
【解决方案2】:

构造函数是假设创建一个对象。由于在 php 中布尔值不被视为对象,因此唯一的选项是 null。否则使用解决方法,即编写一个创建实际对象的静态方法。

public static function CheckAndCreate($identifier){
  $result = self::loadUser();
  if($result === true){
    return new EmailClassNameHere();
  }else{
    return false;
  }
}

【讨论】:

    【解决方案3】:

    为什么不简单地将结果传递给构建对象所需的构造函数,而不是尝试让构造函数有时失败?

    即使有时你可以让它失败,你仍然需要在调用构造函数后检查以确保它确实 did 构造,并且在这些行中,你可以调用 ->loadUser( ) 并将结果传递给构造函数。

    有人告诉我的一个很好的提示,“总是给构造函数构建对象所需的东西,不要让它去寻找它。”

    public function __construct ($emailInTheDatabase, $otherFieldNeeded)
    {
        $this->emailAddress = $emailInTheDatabase;
        $this->otherField = $otherFieldNeeded;
    }
    

    【讨论】:

    • 一堂好课的全部意义在于你不必每次都为获取数据而烦恼——这就是课程的用途。如果我必须在创建类之前获取数据,我就违背了类的部分目的
    • @StephenR 远非如此。无论哪种方式,您每次都在获取数据。唯一的区别是你在哪里做。如果你在构造函数中这样做。然后,您没有其他方法可以获取数据,然后是构造函数中的数据。另一方面,如果你把获取放在构造函数之外,你可以有多种获取用户的方式——通过电子邮件、ID、其他一些复杂的条件,你可以命名它......你甚至可以通过一个查询获取许多用户数据并从中构造许多用户对象。当获取在构造函数中时,您将永远无法做到这一点......
    • @StephenR 我应该补充一点,也许你不明白,你不应该自己实例化用户对象,应该有另一个类负责从数据库中获取数据的有效方式和从中构造用户对象。相信我,我知道我在谈论,我在一家公司工作,他们正好有这个 - 具有接受 id 的构造函数的实体类,然后构造函数将从 db 获取实体数据。数百个,如果不是数千个查询,至少要等待一秒钟才能加载主页。没有人知道如何摆脱它......
    • @slepic 我部分同意你的观点——但构造函数并不是提取数据的唯一地方。查看 PHP 的 DateTime::createFromFormat() 静态函数。也许我的评论措辞不当。我读了这个答案,建议每次都应该在程序代码中提取数据并传入,我认为这是弄巧成拙的。但是这些数据仍然可以在实际的__constructor 函数之外绘制。这个问题的其他答案更好地解释了这一点
    • @StephenR 我明白你的意思。是的,如果获取是在静态方法中而不是在构造函数中,那么它可以工作。除了静态加载器对数据库连接有隐藏的依赖关系。连接凭据必须在类内部进行硬编码,或者从引擎盖下的某个全局变量中提取。这会降低灵活性、可读性和可测试性。 DateTime 类不依赖于可以以不同方式设置的数据库连接,因此可以将其放在静态方法中。我实际上已经发布了我自己对这个问题的答案,请检查一下,看看我会怎么做。
    【解决方案4】:

    构造函数没有返回值;它们完全用于实例化类。

    如果不重组你已经在做的事情,你可以考虑在这里使用异常。

    public function __construct ($identifier = NULL)
    {
      $this->emailAddress = $identifier;
      $this->loadUser();
    }
    
    private function loadUser ()
    {
        // try to load the user
        if (/* not able to load user */) {
            throw new Exception('Unable to load user using identifier: ' . $this->identifier);
        }
    }
    

    现在,您可以用这种方式创建一个新用户。

    try {
        $user = new User('user@example.com');
    } catch (Exception $e) {
        // unable to create the user using that id, handle the exception
    }
    

    【讨论】:

    • 这个答案只提出了构造函数中返回语句的语义问题的解决方案。但它并没有指出这种方法根本上是错误的,不应该以这种方式构造/加载实体。
    • @slepic 我也不会这样做。但是,我认为您会发现在编程世界中的观点和实践差异很大。几个 ORM 在令人惊讶的地方执行查询——例如在属性访问器中。在这种情况下,“应该”和“不应该”是主观的。
    • 多个 ORM 执行查询的位置与实体对象的构造方式完全无关。请告诉我任何以这种方式构造实体的广泛使用的 ORM。您的构造函数方法从根本上是错误的,并且没有任何主观性。这种说法是有客观原因的,我不会在这里重复,因为我已经在我的其他帖子中写过了。随意阅读它们...
    • 您有一组特定的偏好@slepic,这很好。我不同意这些是在每种情况下唯一合理的偏好,因此我不同意它们是客观的。对于“根本上”的含义,我们有不同的看法。谢天谢地,SO 允许多个答案。
    【解决方案5】:

    感谢所有 cmets 和解决方案。这是我为解决问题所做的工作:(我希望它可以帮助其他人)

    // Setup the user ( we assume he is a user first. referees, admins are considered users too )
        try {
          $him = new user ($_emailAddress); 
          // check the supplied password 
          $pass_ok = $him->auth($_Password);
    
          // check the activation status 
          $active_ok = $him->makeActive();
    
        } catch (Exception $e_u) { 
          // try the groups database
          try { 
          $him = new group ($_emailAddress);
          // check the supplied password 
          $pass_ok = $him->auth($_Password);
                  //var_dump ($pass_ok);
    
          // check the activation status 
          $active_ok = $him->makeActive();
          } catch (Exception $e_g) {
              // email address was not in any of them !!
              $pass_ok = false; $active_ok = false;
            }
        }
    

    【讨论】:

      【解决方案6】:

      我不会在结构中投入太多。您应该考虑创建用户(工厂)的静态函数,而不是将所有内容都放在构造函数中。因此,您仍然可以使用您的用户对象,而无需隐式调用加载函数。这将避免你的痛苦。

      public function __construct(){}
      
      public function setIdentifier($value){
          $this->identifier = $value;
      }
      
      public function load(){
          // whatever you need to load here
          //...
          throw new UserParameterNotSetException('identifier not set');
          // ...
          // if user cannot be loaded properly
          throw new UserNotFoundException('could not found user');
      }
      
      public static function loadUser($identifier){
          $user = new User();
          $user->setIdentifier($identifier);
          $user->load();
          return $user;
      }
      

      示例用法:

      $user = new User(); 
      try{
          $user->setIdentifier('identifier');
          $user->load();
      }
      catch(UserParameterNotSetException $e){
          //...
      }
      catch(UserNotFoundException $e){
          // do whatever you need to do when user is not found
      }
      
      // With the factory static function:
      try{
          $user2 = User::loadUser('identifier');
      }
      catch(UserParameterNotSetException $e){
          //...
      }
      catch(UserNotFoundException $e){
          // do whatever you need to do when user is not found
      }
      

      【讨论】:

        【解决方案7】:

        我真的很惊讶,4 年来,22k 的观众都没有建议创建私有构造函数和尝试创建这样的对象的方法:

        class A {
            private function __construct () {
                echo "Created!\n";
            }
            public static function attemptToCreate ($should_it_succeed) {
                if ($should_it_succeed) {
                    return new A();
                }
                return false;
            }
        }
        
        var_dump(A::attemptToCreate(0)); // bool(false)
        var_dump(A::attemptToCreate(1)); // object(A)#1 (0) {}
        //! new A(); - gives error
        

        这样你要么得到一个对象,要么得到一个 false(你也可以让它返回 null)。现在很容易抓住这两种情况:

        $user = User::attemptToCreate('email@example.com');
        if(!$user) { // or if(is_null($user)) in case you return null instead of false
            echo "Not logged.";
        } else {
            echo $user->name; // e.g.
        }
        

        你可以在这里测试它:http://ideone.com/TDqSyi

        我发现我的解决方案比抛出和捕获异常更方便使用。

        【讨论】:

        • @mightyuhu 做到了这一点,并获得了几票。
        • @Wilt 是的,我现在看到了……但他根本没有提到私有构造函数……
        • 我没有看到其他答案。删除?无论如何,这是一个很好的答案,以及我在少数情况下所做的
        【解决方案8】:

        你能做的最好的就是史蒂夫的建议。 永远不要创建除了将构造函数参数分配给对象属性之外的任何工作的构造函数,也许创建一些默认参数,但仅此而已。 构造函数旨在创建一个功能齐全的对象。这样的对象在实例化后必须始终按预期工作。 用户有电子邮件、姓名和可能的其他一些属性。当您要实例化用户对象时,请将所有这些属性提供给其构造函数。 抛出异常也不是一个好方法。异常意味着在异常情况下抛出。即使您最终发现不存在这样的用户,通过电子邮件询问用户也没什么特别的。例如,如果您通过 email = '' 请求用户(除非这是您系统中的常规状态,但在这些情况下 id 建议电子邮件为空),则可能会出现例外情况。 要获取用户对象的所有这些属性,您应该有一个工厂(或者如果您愿意,可以使用存储库)对象(是的,一个对象 - 使用静态的任何东西都是一种不好的做法) 私有构造函数也是一种不好的做法(无论如何你都需要一个静态方法,正如我已经说过的,静态非常糟糕)

        所以结果应该是这样的:

        class User {
          private $name;
          private $email;
          private $otherprop;
        
          public function __construct($name, $email, $otherprop = null) {
            $this->name = $name;
            $this->email = $email;
            $this->otherprop = $otherprop;
          }
        }
        
        class UserRepository {
          private $db;
        
          public function __construct($db) {
            $this->db = $db; //this is what constructors should only do
          }
        
          public function getUserByEmail($email) {
            $sql = "SELECT * FROM users WHERE email = $email"; //do some quoting here
            $data = $this->db->fetchOneRow($sql); //supose email is unique in the db
            if($data) {
              return new User($data['name'], $data['email'], $data['otherprop']);
            } else {
              return null;
            }
          }
        }
        
        $repository = new UserRepository($database); //suppose we have users stored in db
        $user = $repository->getUserByEmail('whatever@wherever.com');
        if($user === null) {
          //show error or whatever you want to do in that case
        } else {
          //do the job with user object
        }
        

        看到了吗?无静态,无异常,构造函数简单,可读性强,可测试,可修改

        【讨论】:

          猜你喜欢
          • 2012-08-07
          • 1970-01-01
          • 2013-10-03
          • 2019-03-23
          • 2015-06-30
          • 2016-10-21
          • 2015-07-08
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多