web-dev-qa-db-fra.com

Renvoyer une valeur dans la fonction constructeur d'une classe

Jusqu'à présent, j'ai une classe PHP avec le constructeur

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

la fonctionnalité de loadUser est de rechercher dans la base de données une adresse e-mail particulière. Lorsque j'ai défini l'identifiant sur un e-mail, je suis sûr qu'il ne se trouve pas dans la base de données; le premier IF est passé et passe au premier ELSE. ici, le constructeur doit retourner FALSE; mais à la place, il renvoie un objet de la classe avec toutes les valeurs NULL!

comment éviter cela? Merci

MODIFIER:

merci à tous pour les réponses. c'était assez rapide! Je vois que la manière OOP est de lever une exception. Donc une seule, ma question change que que dois-je faire avec l'exception ?? Le manuel de php.net est assez déroutant!

    // 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 !!  
        }
    }
46
Anoosh Ravan

Les constructeurs n'obtiennent pas de valeurs de retour; ils servent entièrement à instancier la classe.

Sans restructurer ce que vous faites déjà, vous pouvez envisager d'utiliser une exception ici.

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);
    }
}

Maintenant, vous pouvez créer un nouvel utilisateur de cette façon.

try {
    $user = new User('[email protected]');
} catch (Exception $e) {
    // unable to create the user using that id, handle the exception
}
71
erisco

Le constructeur est supposé créer un objet. Puisque dans php les booléens ne sont pas considérés comme des objets, la seule option est null. Sinon, utilisez une solution de contournement, c'est-à-dire écrire une méthode statique qui crée l'objet réel.

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

Le mieux que vous puissiez faire est ce que Steve a suggéré. Ne créez jamais de constructeurs qui effectuent d'autres tâches que d'attribuer des paramètres de constructeur aux propriétés de l'objet, peut-être en créer des par défaut, mais rien d'autre. Les constructeurs sont destinés à créer un objet entièrement fonctionnel. Un tel objet doit toujours fonctionner comme prévu après son instanciation. Un utilisateur a un e-mail, un nom et probablement d'autres propriétés. Lorsque vous souhaitez instancier un objet utilisateur, donnez toutes ces propriétés à son constructeur. Lancer des exceptions n'est pas non plus un bon moyen. Une exception est censée être levée dans des conditions exceptionnelles. Demander un utilisateur par e-mail n'a rien d'exceptionnel, même si vous finissez par découvrir qu'aucun tel utilisateur n'existe. L'exception pourrait être par exemple si vous demandez un utilisateur par email = '' (sauf si c'est un état normal dans votre système, mais que je suggère plutôt que les emails soient nuls dans ces cas). Pour obtenir toutes ces propriétés pour un objet utilisateur, vous devez avoir un objet de fabrique (ou un référentiel si vous préférez) (oui, un objet - c'est une mauvaise pratique d'utiliser quoi que ce soit statique) Le constructeur privé est une mauvaise pratique non plus (vous aurez besoin d'une méthode statique de toute façon et comme je l'ai déjà dit, la statique est très mauvaise)

donc le résultat devrait être quelque chose comme ceci:

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('[email protected]');
if($user === null) {
  //show error or whatever you want to do in that case
} else {
  //do the job with user object
}

Voir? pas de statique, pas d'exception, constructeurs simples et très lisibles, testables et modifiables

7
slepic

Un constructeur ne peut renvoyer rien d'autre que l'objet qu'il tente de créer. Si l'instanciation ne se termine pas correctement, il vous restera une instance de classe pleine de propriétés NULL comme vous l'avez découvert.

Si l'objet se charge dans un état incomplet ou d'erreur, je suggère de définir une propriété pour l'indiquer.

// 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";
  }
}

Lors de l'instanciation de l'objet, vous pouvez vérifier s'il a un état d'erreur:

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

Une autre alternative (peut-être meilleure) consiste à lever une exception dans le constructeur et à envelopper l'instanciation dans un try/catch.

3
Michael Berkowski

Pourquoi ne pas simplement transmettre les résultats au constructeur nécessaire pour construire l'objet, plutôt que d'essayer de faire échouer le constructeur parfois?

Même si vous pouvez parfois le faire échouer, vous devrez toujours vérifier après avoir appelé le constructeur pour vous assurer qu'il s'agit bien de la construction did, et dans ces lignes, vous pouvez simplement appeler le -> loadUser () et passer les résultats dans le constructeur.

Un bon indice que quelqu'un m'a dit: "donnez toujours au constructeur ce dont il a besoin pour construire l'objet, ne le faites pas le chercher."

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

Je ne mettrais pas trop dans la construction. Vous devriez considérer une fonction statique qui crée l'utilisateur (usine) au lieu de tout mettre dans le constructeur. Ainsi, vous pouvez toujours utiliser votre objet utilisateur sans avoir à appeler implicitement la fonction de chargement. Cela vous évitera de la douleur.

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

Exemple d'utilisation:

$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
}
0
Nico

Je suis vraiment surpris que pendant 4 ans, aucun des 22 000 téléspectateurs n'ait suggéré de créer un constructeur privé et une méthode qui tente de créer un objet comme celui-ci:

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

De cette façon, vous obtenez un objet ou false (vous pouvez également le faire retourner null). Saisir les deux situations est désormais très simple:

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

Vous pouvez le tester ici: http://ideone.com/TDqSyi

Je trouve ma solution plus pratique à utiliser que de lever et d'attraper des exceptions.

0
Al.G.