2

I understand that one can use interfaces to mandate the definition of a function, but I cannot find something that enables one to mandate function calls, such that e.g. if I create a class being a member of another class (via extends, etc), with a function, for that class to automatically ensure that mandatory functions are called in part with that function.

I mean, to clarify further:

class domain {

   function isEmpty($input) {
      //apply conditional logic and results
   }
}
class test extends domain {

   function addTestToDBTable($test) {
      /** 
       *  try to add but this class automatically makes it so that all rules of
       *  class domain must be passed before it can run
       *  - so essentially, I am no longer required to call those tests for each and
       *    every method 
       **/  
   }
 }

Apologies if this appears incoherent by any means. Sure, it seems lazy but I want to be able to force context without having to concern abou

Update:

Okay, to clarify further: in PHP, if I extend and declare a __construct() for a child class, that child class will override the parent __construct(). I do not want this, I want the parent construct to remain and mandate whatever as it pleases just as the child class may do so also.

user784446
  • 489
  • 1
  • 9
  • 22
  • Do you mean interfaces or abstract classes? – Waleed Khan Mar 13 '13 at 20:35
  • Maybe you can make a `addTestToDBTable` (or another function name) inside `domain` then do `parent::addTestToDBTable()`. – gen_Eric Mar 13 '13 at 20:36
  • I mean, for example if a rule is not applied I want it to automatically output an error -- I want rules to be enforced mandatory, without having to apply that to each and every individual function. I want that super class to automatically mandate those rules (e.g. a default or custom set). – user784446 Mar 13 '13 at 20:38
  • @user784446 I think that's what unit testing is for. – Waleed Khan Mar 13 '13 at 20:40
  • @Rocket Hazmat, I understand but my point is that I don't even want to have to do that. I want domain to automatically take care of all that stuff. Quite simply, test should only worry about itself even if applied to the context of domain -- unless I otherwise specify. Every function of that class shouldn't have any explicit code pertaining with domain, so addTestToDBTable just simply has its logic. Because class test is a member of domain, domain governs it by mandating rules. – user784446 Mar 13 '13 at 20:41

3 Answers3

0

I guess it can be done in two different ways.

Aspect Oriented Programming

Have a look here https://github.com/AOP-PHP/AOP

Generate or write Proxy classes

A really simple example could be:

<?php
class A {
    public function callMe() {
        echo __METHOD__ . "\n";
    }
}

class B extends A {

    // prevents instantiation
    public function __construct() {
    }

    public function shouldCallMe() {
        echo __METHOD__ . "\n";
    }

    public static function newInstance() {
        return new ABProxy();
    }
}

class ABProxy {
    private $b;

    public function __construct() {
        $this->b = new B();
    }

    public function __call($method, $args) {
        $this->b->callMe();
        return call_user_func_array(array($this->b, $method), $args);
    }
}

// make the call
$b = B::newInstance();
$b->shouldCallMe();

// Outputs
// ------------------
// A::callMe
// B::shouldCallMe

Hopes this helps a bit.

Bart
  • 17,070
  • 5
  • 61
  • 80
0

Sounds like you want a Decorator.

See This answer for a detailed explanation on how to do it. Note that it does not require a class extension.

Community
  • 1
  • 1
Madara's Ghost
  • 172,118
  • 50
  • 264
  • 308
0

I would use a domain-validating decorator with some doc-block metaprogramming magic. But this is really a job for an entire library, which no doubt exists.

fiddle

<?php
class FooDomain {
    public static function is_not_empty($input) {
        return !empty($input);
    }
}

class Foo {
    /**
     * @domain FooDomain::is_not_empty my_string
     */
    public function print_string($my_string) {
        echo $my_string . PHP_EOL;
    }
}

$foo = new DomainValidator(new Foo());
$foo->print_string('Hello, world!');
try {
    $foo->print_string(''); // throws a DomainException
} catch (\DomainException $e) {
    echo 'Could not print an empty string...' . PHP_EOL;
}

// ---

class DomainValidator {
    const DOMAIN_TAG = '@domain';

    private $object;

    public function __construct($object) {
        $this->object = $object;
    }

    public function __call($function, $arguments) {
        if (!$this->verify_domain($function, $arguments)) {
            throw new \DomainException('Bad domain!');
        }

        return call_user_func_array(
            array($this->object, $function),
            $arguments
        );
    }

    public function __get($name) {
        return $this->object->name;
    }

    public function __set($name, $value) {
        $this->object->name = $value;
    }

    private function verify_domain($function, $arguments) {
        // Get reference to method
        $method = new \ReflectionMethod($this->object, $function);
        $domains = $this->get_domains($method->getDocComment());
        $arguments = $this->parse_arguments(
            $method->getParameters(),
            $arguments
        );
        foreach ($domains as $domain) {
            if (!call_user_func(
                $domain['name'],
                $arguments[$domain['parameter']]
            )) {
                return false;
            }
        }
        return true;
    }

    private function get_domains($doc_block) {
        $lines = explode("\n", $doc_block);
        $domains = array();
        $domain_tag = DomainValidator::DOMAIN_TAG . ' ';
        foreach ($lines as $line) {
            $has_domain = stristr($line, $domain_tag) !== false;
            if ($has_domain) {
                $domain_info = explode($domain_tag, $line);
                $domain_info = explode(' ', $domain_info[1]);
                $domains[] = array(
                    'name'      => $domain_info[0],
                    'parameter' => $domain_info[1],
                );
            }
        }
        return $domains;
    }

    private function parse_arguments($parameters, $values) {
        $ret = array();
        for ($i = 0, $size = sizeof($values); $i < $size; $i++) {
            $ret[$parameters[$i]->name] = $values[$i];
        }
        return $ret;
    }
}

Output:

Hello, world!
Could not print an empty string...
Waleed Khan
  • 11,426
  • 6
  • 39
  • 70
  • Apologies, I'll have to read into that more thoroughly. However, I've thought about it some more and quite essentially, the parent class must either be able to "know" the methods of the child class, or the child class must be able to "know" it's own methods (i.e. for the constructor to register its methods). I really don't know how to do that. – user784446 Mar 13 '13 at 23:12
  • @user784446 PHP has reflection, but it's slow so I wouldn't use it during production. You can get the methods of a class by [making a `\ReflectionClass`](http://www.php.net/manual/en/class.reflectionclass.php) and calling [its `getMethods` method](http://www.php.net/manual/en/reflectionclass.getmethods.php). The real issue is the fact that it's impossible to intercept all method calls (only the ones that don't exist), so either you have to make all your methods private or you have to use a decorator. – Waleed Khan Mar 14 '13 at 01:36
  • @walheed khan I can use get_class_methods($class) but there's still the problem of the class construct. – user784446 Mar 14 '13 at 02:14
  • @user784446 If both a parent class and a child class can know the child class's methods, where is the issue? – Waleed Khan Mar 14 '13 at 02:18