开发者

How to implmenet this mechanism using Abstract Class in PHP?

From the past two days i have been diving into the concepts of OOPS in PHP, and i found Abstract Class to be very useful concept and i wanted to implement it in my application. and here is why i want to implement it.

My Application Consist of Several Unstructured Pattern which uses several different classes without any hierarchies, due to this i have to use several repeated codes. i want to cut this all up and structure it properly, basically what i want to do is

  1. Define a parent class which is not instantiable from outside.
  2. Define all properties in this parent class so that i can re-use the same property for different child classes.
  3. Define re-usable class methods and objects so that child class can use it without the need of defining it again and again (Inheritance).

Basically My Abstract class should be capable of inheriting the re-usable methods and properties and at the same time it should act as an Interface.

To demonstrate you clearly let me show you some sample codes which i have been using.

public  $error = array(); //used to handle error mechanism and to hold the errors.
private $data = array();  //used with Accessor Methods (__set and __get)
private $validate;        //holds Validate Object
private $dbh;             //holds Database Object

public function __set($property, $value) {
    if( in_array($property, $this->data)) {
        return $this->data[$property] = $value;
    } else {
        return false;
    }
}

public function __get($property) {
    return 'Access Denied to Class Property [ '.$property.' ]';
}

Above codes are repeated for almost every class, this is the reason i want to define it once in a parent class and control the mechanism from there.

As i am still Novice to Many OOPs concept i am unable to understand how do i achieve what i want using Abstract Class. below is the sample code i tried using which ofcourse is wrong for declaring an abstract method.

abstract class Property {
    protected $error = array();
    protected $data = array();
    protected $dbh;
    protected $validate;

    abstract protected function connectDB($dbhandle) {
        return $this->dbh = $dbhandle;
    }

    abstract protected function setValObj($valObj) {
        return $this->validate = $valObj;
    }

    public function __set($property, $value) {

    }

    public function __get($proper开发者_运维百科ty) {

    }
}

here is what i want to do.

  1. When a child class is initiated it should be forced to define methods as declared in abstract class.

  2. A Child class should only be able to call and pass the arguement but not extend an abstract method. the mechanism should be handled by parent class. this is what i tried to do in my code.

i know i am missing something, or might be i have not got the concept right, could somebody explain me what exactly should i be doing to achieve the same result.


1 . Define a parent class which is not instantiable from outside.

All abstract classes can not be instantiated, only extended. So this is what you already have:

abstract class Property {

On to the next:

2 . Define all properties in this parent class so that i can re-use the same property for different child classes.

Just write them into that abstract class (often called base class or abstract base class or template class as well). All classes extending from a base class, will have access to protected or public member methods and properties. If you make a variable or function private, it's only available to code in the base class.

So just define all you want to have in the base class to be shared amongst all extending classes and you only need to type it once.

3 . Define re-usable class methods and objects so that child class can use it without the need of calling it again and again (Inheritance).

This works automatically. As you extend from the base class, the public and protected class methods defined therein are automatically accessible through the extending class. You do not (but you can unless specified with final) need to add that function again to make it available. E.g. all public methods from the base class are automatically available publicly on all classes that extend from it.


Then you continue:

here is what i want to do.

1 . When a child class is initiated it should be forced to define methods as declared in abstract class.

You can do so by defining these needed methods as abstract. Abstract methods needs to be implemented by the extending class. However you can not put code into abstract methods in the base class. That's left there for the extending class.

2 . A Child class should only be able to call and pass the arguement but not extend an abstract method. the mechanism should be handled by parent class. this is what i tried to do in my code.

If you want to prevent a subclass to overwrite a function, declare it as final in your base class. Final methods can not be further extended.

But probably you want to do something that is technically not possible, e.g. prevent that a method can be extended while you require that is should be extended.

In your code you're using magic functions to access the properties / values. Those don't count in the sense that their name changes. So you loose the control of the inheritance for a bigger part of your class design.

However, you can implement array access to offer getter/setters. It's bound to a concrete interface and you then can disallow access through the base class and prevent extending of this area for classes that will extend from it.

Let me know if you would like to have some example code, probably SPL is new to you as well.

Provide variable inheritance via ArrayAccess

As you've been running into the problem that inheritance can not be easily used on the magic function __get() and __set() which are available, I had the idea to make that part of the access concrete that does not change (get, set) while it's still possible to name the property variable. An interface that is available with PHP that already does this is ArrayAccess. It was designed to give access to properties via the style we know from standard php arrays ([]) and it's normally used for that. But for this example it has the benefit to already provide an interface as well that fits the general need.

First a demonstration how such a class behaves in use:

# give birth to the object
$object = new PropertyClass; // one of your property classes

# get:
$value = $object['propertyA'];

# set:
$object['propertyA'] = 'new value';

# unset:
unset($object['propertyA']); // and gone ;)

# isset:
isset($object['propertyA']); // true / false

Okay, as this shows, this looks like an array, but is an object. The rest of $object works as known, so this is no limitation, but an addition.

As you can imagine already with this code, there must be a get and set routine as well for reading and setting the properties values like with __get() and __set(). Additionally there must be something for isset and unset, so four. This is the interface definition of ArrayAccess:

ArrayAccess {
    /* Methods */
    abstract public boolean offsetExists ( mixed $offset )
    abstract public mixed offsetGet ( mixed $offset )
    abstract public void offsetSet ( mixed $offset , mixed $value )
    abstract public void offsetUnset ( mixed $offset )
}

You can extend from that in PHP by implementing the interface. That's not extends but implements. This works with every interface in PHP, but this interface is something special as well. It's provided by the SPL/PHP itself and in the moment a class of yours actually implement the functions, the functionality as described in the code above is automatically added to your class.

As those functions are available publicly, you could call them with their name as well naturally.

So actually this interface qualifies for a properties object as you want to build one and will give you an interface you can put your constraints on.

So the only question left is: How can this look like for your properties class?

Implementing ArrayAccess to a variable properties class

<?php
/**
 * Property Object base class based on ArrayAccess
 */
abstract class PropertyObject implements ArrayAccess
{
    /** Interface Methods */
    /**
     * implementing classes must return all names of their properties
     * in form of an array.
     */
    abstract protected function returnNames(); 
    
    /** class */
    
    /**
     * value store
     * 
     * @var array
     */
    private $store = array();

    /**
     * 
     * By this design, properties can only contain A-Z and a-z.
     *
     * look like.
     * 
     * @return bool
     */
    private function isValidPropertyName($name) {
        return ctype_alpha($name);
    }
    
    private function checkOffsetArgument($offset) {
        if ($this->isValidPropertyName($offset)) return;
        throw new InvalidArgumentException(sprintf('"%s" is not a valid property name.', $offset));
    }
    
    private function setNames(array $names) {
        foreach($names as $name) {
            $this->checkOffsetArgument($name);
        }
        $len = count($names);
        $this->store = $len 
            ? array_combine($names, array_fill(0, $len, null)) 
            : array()
            ;
    }
    
    
    /**
     * final constructor to obtain control
     */
    final public function __construct() {
        $this->setNames($this->returnNames());
    }
    
    /**
     * ArrayAccess impl.
     * 
     * @return bool
     */
    public function offsetExists($offset) {
        $this->checkOffsetArgument($offset);
        return array_key_exists($offset, $this->store);
    }

    /**
     * ArrayAccess impl.
     * 
     * @return mixed
     */
    public function offsetGet ($offset) {
        $this->checkOffsetArgument($offset);
        return $this->store[$offset];
    }
    
    /**
     * ArrayAccess impl.
     */
    public function offsetSet($offset, $value) {
        $this->checkOffsetArgument($offset);
        if (!$this->offsetExists($offset)) {
            throw new InvalidArgumentException(sprintf('Property "%s" can not be set.', $offset));
        }
        $this->store[$offset] = $value;
    }
    
    /**
     * ArrayAccess impl.
     */
    public function offsetUnset($offset) {
        $this->checkOffsetArgument($offset);
        unset($this->store[$offset]);
    }
}


/**
 * I feel so concrete.
 */
class ConcreteType extends PropertyObject
{
    protected function returnNames() {
        return array('propertyA');
    }
}

$obj = new ConcreteType;

var_dump($obj['propertyA']); # NULL, maybe you need other default values.

$obj['propertyA'] = 'hello';

var_dump($obj['propertyA']); # string(5) "hello"

var_dump(isset($obj['propertyA'])); # bool(true)

// this will trigger an exception
try {
    $obj['XProperty'] = 'good night.';
} catch (Exception $e) {
    var_dump($e->getMessage()); # string(36) "Property "XProperty" can not be set."
}

// the following might be unwanted but can be prevented in base class:

unset($obj['propertyA']);

var_dump(isset($obj['propertyA'])); # bool(false)


I think you need to implement mixin interface. According to my knowledge PHP does not support this natively. Some PHP frameworks (like Yii framework) implemented it by itself. I found one example here. But I am sure you will be able to find better examples.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜