2016-04-11 6 views
4

Es gibt bereits viele Fragen nach PHP 7 Rückgabetyp Hinting und warum es nicht möglich ist, null zurückzugeben, wenn eine Klasse als Rückgabetyp definiert wurde. Wie dieser: Correct way to handle PHP 7 return types. Die Antworten sagen normalerweise, dass es kein Problem ist, als ob eine Funktion eine Klasse zurückgeben sollte, dann ist das Zurückgeben von null wahrscheinlich sowieso eine Ausnahme. Vielleicht habe ich etwas verpasst, aber ich verstehe nicht wirklich warum. Zum Beispiel wollen sie eine einfache Benutzerklasse sehen:PHP 7 Rückgabetyp Hinting

class User 
{ 
    private $username; // mandatory 
    private $password; // mandatory 
    private $realName; // optional 
    private $address; // optional 

    public function getUsername() : string { 
     return $this->username; 
    } 

    public function setUsername(string $username) { 
     $this->username = $username; 
    } 

    public function getPassword() : string { 
     return $this->password; 
    } 

    public function setPassword(string $password) { 
     $this->password = $password; 
    } 

    public function getRealName() : string { 
     return $this->realName; 
    } 

    public function setRealName(string $realName = null) { 
     $this->realName = $realName; 
    } 

    public function getAddress() : Address { 
     return $this->address; 
    } 

    public function setAddress(Address $address = null) { 
     $this->address = $address; 
    } 

} 

In unserer Anwendung ist es völlig legal einen Benutzer ohne realname und/oder ohne eine Adresse zu haben. Ich kann sogar die Felder realName und address auf null setzen - auch wenn die obige Lösung nicht optimal ist. Und auf unserer Profilseite möchte ich einen Hinweis anzeigen, wenn die Adresse null (leer) ist.

Aber das ist nur 1 Beispiel. Unsere Anwendung hat mehr als 100 Datenbanktabellen und entsprechende PHP-Klassen und fast alle von ihnen haben optionale Felder. Das Schreiben eines try-catch-Blocks, anstatt einfach null zu prüfen, um optionale Felder zu behandeln, scheint für mich ein wenig suboptimal zu sein.

Was ist eine gute Lösung für das obige Beispiel?

+0

Sie könnten nur die Variablen leer Standardwerte initialisiert wird (wie Strings ‚‘ und Adresse zu einer Adresse Objekt mit leeren/ungültig/Standardwerten. Sie können auch Ihre Klassen verwenden könnten sie von einem Wrapper aufruf, die die Ausnahme abfangen würden, wenn Sie sind wieder eine null. – Nadir

+0

ich glaube nicht, getAddress(). getCity === ‚‘ ist besser als einfach getAddress überprüfen() == null. Außerdem Check kann kompliziert sein. Wie zum Beispiel, wenn der zipcode optional dann kann ein neuer Entwickler einen Code schreiben:.. getAddress() getZip() === ‚‘ und er/sie denkt, dass die Adresse fehlt, aber nur die zipcode fehlt – Vmxes

+0

In Ihrem Beispiel, was würden Sie die Telefonvorwahl der erwarten Verhalten zu sein, wenn es 'getRealName()' aufruft, wenn '$ this-> realName' null ist? Muss es den Wert prüfen und handhaben, wenn es null ist? Wenn ja: Ihre Klasse sollte das tun. Wenn Sie wirksam sind Wenn NULL-Strings für viele String-Operationen wie leere Strings behandelt werden, sollten Sie sie wahrscheinlich auf einen leeren String und nicht auf null setzen. Die meisten Situationen können so gelöst werden. Das heißt, ich denke, dass PHP in seiner Durchsetzung dies unangemessen rechtfertigt. –

Antwort

2

Das Problem besteht darin, dass Ihr Objekt den OOP-Regeln nicht folgt.

Zuerst getters and setters are evil, weil sie die interne Struktur des Objekts freilegen. Sie führen nur die unendliche Möglichkeit ein, sich auf die interne Benutzerobjektstruktur für den Rest Ihres Systems zu verlassen. Der Zweck des Objekts besteht darin, die Implementierungsdetails zu verbergen und die Schnittstelle bereitzustellen, um diese versteckten Daten zu bearbeiten.

Und null is evil (oder a billion dollar mistake), weil es den Code viel weniger zuverlässig macht. Es führt den Sonderfall ein (Null Rückgabewert) und es ist notwendig, diesen Sonderfall im Code mit Ihrem Objekt zu behandeln. Das Schlimmste ist, dass wenn Sie vergessen, diesen "Null" Sonderfall zu behandeln (oder wenn Sie nicht wissen, dass es einen Sonderfall gibt), Sie den Fehler nicht sofort erhalten. So erhalten Sie den versteckten Fehler, der sehr zeitaufwendig sein kann, zu finden und zu beheben.

Praktisch sehe ich folgende Optionen (in allen Fällen - entfernen Sie die Getter):

Option 1) Holen Sie sich den Schnappschuss der Profildaten in irgendeiner einheitliche Form

$user->getProfileData() { 
    return [ 
     'username': $this->username, 
     'realName': $this->realName ? $this->realName : '', 
     'address': $this->address ? $this->address : 'Not specified' 
     ... 
    ]; 
} 

Dies ist immer noch eine Art eines Getters, aber Sie transformieren alle Daten in die einheitliche Form (Strings). Selbst wenn Sie eine ganze Zahl von Float-Feldern (wie Alter oder Höhe) in Ihrer Datenbank haben, würden Sie hier immer noch Strings zurückgeben, so dass der Rest Ihres Systems nicht von den tatsächlichen Interna des Objekts abhängt.

Die leere Zeichenfolge (oder ein Sonderwert wie "nicht angegeben") für optionale Felder fungiert auch als eine Art "Null-Objekt". Es ist auch möglich, die zurückgegebenen Werte in kleine Felder-Objekte zu verpacken und tatsächlich das Null-Objektmuster für optionale Felder zu verwenden.

In dem Code, der die Klasse verwendet, sollten Sie nicht mit dem speziellen "Null" -Fall umgehen, Sie können nur Felder überlappen und Strings anzeigen (einschließlich solcher, die leer sind).Machen

Option 2) das Objekt selbst verantwortlich für die Präsentation

$user->showProfile($profileView) { 
    $profileView->addLabel('First Name'); 
    $profileView->addString($this->username); 
    ... 
} 

Hier halten Sie die internen Details im Inneren des Objekts, aber jetzt ist es zu viel, so jemand sagen könnte es jetzt die SRP verletzt.

Option 3) Erstellen Sie spezielles Objekt verantwortlich für die Präsentation

$userPresentation = $user->createPresentation() 
// internally it will return new UserPresentation($this->username, this->realName, $this->address, ...); 
// now display it - generate the template and insert it into the view 
<? echo $userPresentation->getHtml(); ?> 

Hier bewegen Sie die Präsentationslogik in das separate Objekt. Das Benutzerobjekt und seine Präsentation sind eng miteinander verbunden, aber der Rest des Systems weiß nichts über die Benutzerobjekt-Interna.

+0

Sehr detaillierte Antwort, aber ich denke, im Falle einer einfachen Entity-Klasse verletzen alle 3 Optionen SRP. Ich möchte nicht immer einen neuen Getter und eine neue Klasse für eine Untergruppe von Benutzereigenschaften erstellen, je nachdem, was eine Anruferklasse benötigt. Und natürlich möchte ich keine Präsentationslogik in meiner Entitätsklasse. – Vmxes

+0

@Vmxes das Problem mit Ihrer aktuellen Implementierung ist, dass es eine der grundlegenden OOP-Prinzipien - Kapselung verletzt. Obwohl Sie Ihre Felder als 'privat' definieren, öffnen Sie diese Felder für die Welt mit Gettern und Sätzen. Und Sie geben Nullen zurück, so dass Sie dann nach Sonderfällen im gesamten Code suchen müssen. Sobald Sie anfangen, darüber nachzudenken, wie Sie diese Fehler loswerden und andere Ansätze verwenden können, werden Sie sehen, dass der Rest Ihres Codes auch einfacher und einheitlicher wird. Meine Beispiele sind nur einige Optionen, die ich mir vorgenommen habe, siehe auch die Artikel, die ich verlinkt habe. –

0

Ich habe umarmte vor kurzem die Idee, dass null bedeutet ein Problem, es ist so, dass ich weiß, dass, wenn ich es sehe, es bedeutet etwas schief ging.

In diesen Situationen neige ich dazu, die Null Object design pattern zu verwenden.
Also, um die Adresse zu behandeln, würden Sie so etwas wie erstellen:

class NullAddress implements AddressInterface { } 

Die Address Klasse müsse auch AddressInterface implementieren.
Dann getAddress() würde wie folgt aussehen:

public function getAddress(): AddressInterface { 
    return $this->address ?? new NullAddress(); 
} 

Eine Funktion getAddress() Aufruf kann dann für einen Nullwert überprüfen, indem Sie:

if ($user->getAddress() instanceof NullAddress) { 
    // Implement result of empty address here. 
} 

Für Streicher Handhabung eine Rückkehr leere Zeichenfolge hat mir gute Dienste geleistet, so weit . Ich verwende einfach empty(), um es zu überprüfen.