views:

32

answers:

4

If I want to create a new object that needs certain informations like a product id or something like that but the input is bad how can I elegant manage such a case?

class Product
{
function __construct($id)
    {
    if(is_invalid_id($id))
        { return false; }
    }
}

If I initialize it this way I still get an object (since return inside a constructor doesn't return anything). Another way would be to use exceptions which I then can catch but that's kinda unelegant. 3rd option is to use a static function which then checks the input and then returns the object.

class Product
{
static function init($id)
    {
    if(is_invalid_id($id))
        { return false; }
    return new self($id);
    }

private function __construct($id)
    {
    $this->id = $id;
    }
}

$product = Product::init($productId);

The problem here is when I try to extend the class. Either I have to create a init() method for every class I extend (even if it is the exact same code) or return new self() always returns an instance of the parent class.

+3  A: 

Throw an exception. Not sure why you consider it unelegant. Man, things were unelegant before exceptions (FALSE, -1, null)

webbiedave
I have to agree. That's the purpose of exceptions.
Christian Mann
I can understand the reluctance of using exceptions. They are not common in PHP programming and require a way of thinking many PHP programmers aren't familiar with.
staticsan
Fear of the unknown, eh? Well, they're quite friendly once you get to know them and are definitely the way to go. Accept exceptions! :)
webbiedave
A: 

I'll give you a non-standard workaround that's universally frowned upon by purists: the hybrid constructor

And it's even more evil than it sounds, because it's actually just a wrapper procedure:

function Product($i) {
    $prod = new Product($i);
    return $prod->valid() ? $prod : new InvalidProduct();
}

class Product {
    function __construct() { ... }
}
class InvalidProduct extends Product implements Stub { }


$new_prod = Product("oops123");   // what will it be?

It simply verifies the object instantantly. If there is something wrong, and now here comes the trick, return a specific stub or NULL object. It might implement a few of the interfaces, but generally cause no side-effects or print an error message once it's inevitable. This concept basically hinges on the viability of carrying a stub object around. It's sometimes more sensible to the application logic to have such a specialized instance than to use decorative test logic.

And then there's the second alternative: just bail with an exception.

mario
Function != class. It's two namespaces in PHP. But I see where the ambiguity comes from. I shall add...
mario
Oh. –––––––––––
webbiedave
+1  A: 

Your third option is the Factory Pattern.

As you've noticed, the downside is that each class that needs this kind of check generally needs it's own factory method.

staticsan
+1 and hurray for factories. They really do make live easier on bigger projects, especially when working with classes / code created by your coworkers.
Wrikken
Wrikken you're getting way too excited lol. He'll still be using exceptions, though... which is good!
webbiedave
I should have mentioned that ORMs can benefit hugely from factories because the initialization code is very similar for all objects and responds well to parameterization. I've done in two different frameworks both times to great effect.
staticsan
+1  A: 

For the problem with self:: you might be able to use late static binding (PHP5.3+):

<?php

class Product
{
static function init($id)
    {
    if(false)
        { return false; }
    $s = get_called_class();
    return new $s($id);
    }

private function __construct($id)
    {
    $this->id = $id;
    }
function getId()
    {
    return "Product-$this->id";
    }
}


class Headphones extends Product
{
function getId()
    {
    return "Headphones-$this->id";
    }
}

$c1 = Product::init(1);
$c2 = Headphones::init(1);
printf("c1 is %s, c2 is %s\n", $c1->getId(), $c2->getId());
// Prints: c1 is Product-1, c2 is Headphones-1
?>
Robin
This is exactly what I was looking for. Thanks!