Jump to content

__call Magic Function.....Proposed New Magic Function 4 Lazy Developers Like Me?


TravisJRyan

Recommended Posts

Today I was unit testing a class I wrote and found myself copying and pasting a lot of the same code to output what I needed to know from each method.  What I really needed to know was, what method was called, what was calling it, what were the params passed into the method, and what did the method respond with.

 

I realized there are a lot of unit testing tools, frameworks and what not that I am sure I am not familiar with, but something as simple as it was doesn't call for a huge test case to be created on some tool or framework, waste of time for me since I am probably not familiar with it.

 

So to solve my issue, I renamed all my method names to have a single "_" in front of it and added the __call() magic method.  The idea of this was to do exactly what I mentioned above, capture the information I needed.  It was either that or copy and paste similar code all over the place again which I really hate doing.  I know I know, tough task, copy, paste.. blah blah, so I'm lazy, so what!

 

My thought was, it would be nice of __call() was always called OR some other magic method like __precall __postcall (weak names, but im dead on creativity at the moment) to simply be able to do something prior to ANY call to a method within the class that has nothing to do with __construct __destruct.  This would have information that I would consider handy available to process on an as needed basis. 

 

I could see this primarily used for unit testing and well, perhaps if other options are available in this method, to better manage the object in some way shape or form.

 

Example:

<?php

class testClass {

    public function __call( $method, $params ) {
        return $this->$method( "You will not see this: " . $params );
    }

    public function MethodName( $params ) {
        return "My Params Are: {$params}";
    }
}
$test = new testClass();
$test->MethodName( "My Params" );
?>

 

In this example you can see the __call() is never called because the method name we are calling exists in the class so __call is never invoked.

 

My Work Around Example:

<?php

class testClass {

    public function __call( $method, $params ) {
        $newMethodName = "_" . $method;
        return $this->$newMethodName( "You WILL see this: " . $params );
    }

    public function _MethodName( $params ) {
        return "My Params Are: {$params}";
    }
}
$test = new testClass();
$test->MethodName( "My Params" );
?>

 

To get the results I needed for multiple methods, renaming all the method names is an option and pre-pending a standard string to the call from __call method work, but make method names and code look unappealing.

 

My thoughts:

<?php

class testClass {

    public function __precall( $method, $params ) {
        // Error log some information
        error_log( "Method called: {$method} \n" );
        error_log( "Params" . print_r( $params, true ) );
    }

    public function __postcall( $method, $response ) {
        //Error log the method's response
        error_log( "Response was:\n" );
        error_log( print_r( $response, true ) );
    }

    public function MethodName( $params ) {
        return "My Params Are: {$params}";
    }
}
$test = new testClass();
$test->MethodName( "My Params" );
?>

 

Here I am envisioning my logging to have the method name that was callled, params passed and its response.  Seems simple in the example, but when you have 30+ methods with similar inputs and responses, I believe this could be beneficial.

 

 

The main question:

If you guys made it this far, you may have an opinion as to whether or not you would consider making or proposing to make __call ALWAYS called if it exists in an object or perhaps an additional magic function for routing all "traffic" in and out of an object through useful or not.  In my case it would have been especially handy for my unit testing, but have yet to brainstorm ideas as to what else this could be useful for.

 

Please note that all the code above I hand typed just now and have not tested, if I have a type-o, parse error, my bad, didn't test it but should get the point acrossed

Hell, maybe it already exists and myself and everyone else in my office is unaware of it and I may be able to pass on a little information.

 

Link to comment
Share on other sites

There is a __call() magic function already. =P  You can do this:

 

    public function __call($method, $args)

    {

        if (!method_exists($this->_obj, $method)) {

            throw new Exception("unknown method [$method]");

        }

 

        return call_user_func_array(

            array($this->_obj, $method),

            $args

        );

    }

 

Found a posting on the PHP manual page with that example, but it IS a magic method.

Link to comment
Share on other sites

There is a __call() magic function already. =P  You can do this:

 

    public function __call($method, $args)

    {

        if (!method_exists($this->_obj, $method)) {

            throw new Exception("unknown method [$method]");

        }

 

        return call_user_func_array(

            array($this->_obj, $method),

            $args

        );

    }

 

Found a posting on the PHP manual page with that example, but it IS a magic method.

 

The magic method __call I am well aware of, but the scope of if __call can be invoked is dependent upon whether or not the method exists in the class. 

 

If MethodA exists in the class, __call magic method is bypassed and never used. 

 

If MethodB is NOT defined in the class, the magic method __call is invoked if it exists and does "its magic" if it can.

 

If the method name exists in the class whether it be protected, public, private, __call is always bypassed.  If and outside object is called a private method, it seems __call should be called instead of an exception stating "You are bad trying to call a private method."

 

It seems a simple solution would be to force __call to be invoked if a method name is private or protected.  That may work it seems.

 

Link to comment
Share on other sites

Today I was unit testing a class I wrote and found myself copying and pasting a lot of the same code to output what I needed to know from each method.  What I really needed to know was, what method was called, what was calling it, what were the params passed into the method, and what did the method respond with.

 

This is what a debugger is for.

 

So to solve my issue, I renamed all my method names to have a single "_" in front of it and added the __call() magic method.  The idea of this was to do exactly what I mentioned above, capture the information I needed.  It was either that or copy and paste similar code all over the place again which I really hate doing.  I know I know, tough task, copy, paste.. blah blah, so I'm lazy, so what!

 

Copy and paste means a maintenance nightmare, you were right to do something about it!

 

My thought was, it would be nice of __call() was always called OR some other magic method like __precall __postcall (weak names, but im dead on creativity at the moment) to simply be able to do something prior to ANY call to a method within the class that has nothing to do with __construct __destruct.  This would have information that I would consider handy available to process on an as needed basis. 

 

 

Disagree. I think this exposes a fundamental flaw in your design process.

 

 

The main question:

If you guys made it this far, you may have an opinion as to whether or not you would consider making or proposing to make __call ALWAYS called if it exists in an object or perhaps an additional magic function for routing all "traffic" in and out of an object through useful or not.  In my case it would have been especially handy for my unit testing, but have yet to brainstorm ideas as to what else this could be useful for.

 

 

I think your ideas on how to unit test have been clouded.

 

Please note that all the code above I hand typed just now and have not tested, if I have a type-o, parse error, my bad, didn't test it but should get the point acrossed

Hell, maybe it already exists and myself and everyone else in my office is unaware of it and I may be able to pass on a little information.

 

Unit testing doesn't need to be complicated.

 

Generally if you have a module or service, the proper way to unit test it is to provide a stub. Using this stub you ensure the core of the service/module works as expected.

 

I don't see anywhere where I would require to know the input and the parameters that are being passed. I already know! I am passing them!

 

I hope this doesn't come off as being harsh, it's definitely not my intention.

Link to comment
Share on other sites

This is what a debugger is for.

 

I believe that yes, a debugger works just as well, but is a waste of time stepping through crap.  If I can dump stuff on run time and be quick about things, Id rather do that on my dev box then step through crap.  I have the zend debugger installed on my dev box but I think have used it a handful of times.

 

Disagree. I think this exposes a fundamental flaw in your design process.

 

What fundamental flaw are you referring to?

 

---------------------

 

What it gets down to is this.  A private method shouldn't and isn't accessible by any other object...right? Would that mean Object A looking at Object B's private method be ok? (this sounds horrible)  Shouldn't a private method but invisible so to speak to anything outside of itself.

 

With that thought:

 

Why would we throw a fatal error when trying to call it if according to object A, the private method in object B isn't accessible.  Wouldn't it make sense in the case that it is not visible by something outside itself to invoke the __call() magic method even if __call simply calls the private method by that name? 

 

I suppose the real question isn't my clouded jankie way of unit testing (which for the record I never did, just made it up for discussion sake. . . lol ) or debugging, but whether or not PHP is operating as it should when it tosses a fatal on attempts to access a defined method in a class that is either protected or private when a __call method is present.

 

 

Link to comment
Share on other sites

You're talking about unit testing from a method perspective. You don't unit test methods. That's silly. You unit test the services your object/module provides, which, tests the overall functionality.

 

What fundamental flaw are you referring to?

 

The one that breaks encapsulation/requires knowledge of how your classes are implemented.

Link to comment
Share on other sites

You're talking about unit testing from a method perspective. You don't unit test methods. That's silly. You unit test the services your object/module provides, which, tests the overall functionality.

In this particular case today we have something in our application that sends various emails.  Every method requires similar parameters and the responses should be the same.  So the idea was to create a simple script that ripped through all the method names, pass in an id, get a response.  Easily logged with a script or debugger during development agreed.  The scope of a private/protected method inside of a class seems off point however. 

 

The one that breaks encapsulation/requires knowledge of how your classes are implemented.

Its not a requirement of knowledge on how a class is implemented, but perhaps a "convenience" option should you need that type of information..

Link to comment
Share on other sites

That's a really interesting idea and a different perspective on the situation.

 

Tell me, though. From an outsiders perspective, what would you think if I said the following:

 

"Every method requires similar parameters and the response should be the same"

 

I think you would say -- "Sounds like theres a bit more abstraction to be done."

 

Or am I offbase?

Link to comment
Share on other sites

That's a really interesting idea and a different perspective on the situation.

Tell me, though. From an outsiders perspective, what would you think if I said the following:

"Every method requires similar parameters and the response should be the same"

I think you would say -- "Sounds like theres a bit more abstraction to be done."

Or am I offbase?

 

haha. .agreed, and ya, I would probably think that as well.  I suppose my example was pretty lame.

Link to comment
Share on other sites

Semantics aside, I think it's valuable for everyone to know the information you provided about __call() and how you use it.

 

I am generally not a fan of these magic methods provided by PHP. __call() __get() and __set() seem very counter intuitive (to me) but I can certainly understand why people would use them.

 

Link to comment
Share on other sites

So would you agree that calling a method of an object that id declared as private should not throw and exception if the __call magic function is present in the class and can be called if the object's method it is calling is out of its scope?

Link to comment
Share on other sites

Well, no.

 

If you consider what you learned, then it should make sense that it should not break the barrier of what 'private' is.

 

You basically proved:

 

$x->foo('bar');

 

is the same as

 

$x->__call("foo", "bar");

 

So, by that logic, if foo is declared private, why provide a means of accessing it?

Link to comment
Share on other sites

I agree with keeB.  If you make a call to a private/protected method, it should never go to the __call() method.  Just because it's private/protected doesn't mean it's invisible to something outside the object.  And if I make the mistake of calling a private method that I thought was private I definitely want PHP to throw me that error when I have error reporting on.

 

Personally, I see very little use of __call() except for a few rare conveniences.  I do find __get and __set to be quite nice though.

Link to comment
Share on other sites

__get & __set break encapsulation just as he was arguing. . .so php has an inconsistency with its magic methods. 

 

What happens when you reference a private property of an object? It goes to __get() or __set() where you CAN break the theory of encapsulation. If a property is private, "why provide a means of accessing it?"

 

After discussions at work and everything, its come down to that.  Allow the break in encapsulation in one method, why not all of them?  or fix the others.

Link to comment
Share on other sites

Just for completeness, Travis and I just had a conversation about this issue over AIM.

 

I explained, __get() and __set() not being able to access private methods would be practically useless. Since all class attributes should be declared PRIVATE by convention. Tha'ts the reason you create $x->getAttr(); instead of $x->attr = "lol";

 

Private methods can be exceptionally fragile. They weren't created to be exposed to the user for a reason. If you wanted a user to be able to call them, you'd make them Public!

 

 

 

Link to comment
Share on other sites

I find __get and __set to be nice alternatives to writing separate methods in certain instances.  For instance, in the new form class I'm developing, each element is a separate object.  Why write a 'setValue', 'setName', 'setClass' and 'setAttr' methods (and the 'get' inverses) when I can just call __get and __set?  There are just three or four methods and properties per element object.

 

Within each __get and __set method, you can control, to some extent, what happens.

 

I'm still new to OOP though.  Is it still a significantly better thing to NOT use the magic functions in the aforementioned scenario?

Link to comment
Share on other sites

In my opinion, it's better to stick to a defined structure. By a significant margin..

 

First, OOP is how I choose to work and organize myself. I see a Form as any other Composite[1] object. I could also see developing a Form using the Decorator pattern [2]. This is instinctual, because I don't first think in terms of language features, I think in terms of overall design.

 

What are the benefits? Well, really, to be truthful... none. I think an input tag will always have the same attributes it has now. You'll still have to come up with a way to actually feed the data to your object to call the ->draw() method. You could do it with the __construct() method, but, if a new attribute you want to support is added in this case you have to update your __construct() definition, which really kills downstream apps. You could set a default value on to it (and all other attributes for that matter) to mitigate the damage, but it's kind of ugly.

 

You see, application design a list of trade offs. If you're not flexible and playing to the strengths of your application and instead allow yourself to be unflexible, you really end up with crap. I think that's why a lot of people fail when it comes to conceptualizing OOP. It takes a lot of work and a lot of planning to implement it properly, but I think the payoff is absolutely huge in the end.

 

Last I'll say is my choice in design has been heavily influenced by the man who wrote the following article[3]. I've linked it quite a few times in the past. He wrote an amazing book called Holub On Patterns [4] which brings new meaning every time I read it.

 

[1] http://en.wikipedia.org/wiki/Composite_pattern

[2] http://en.wikipedia.org/wiki/Decorator_pattern

[3] http://www.javaworld.com/javaworld/jw-09-2003/jw-0905-toolbox.html

[4] http://www.amazon.com/Holub-Patterns-Learning-Design-Looking/dp/159059388X

Link to comment
Share on other sites

keeB, your responses in this OOP forum are always helpful, to me at least.

 

The whole idea of looking at programming in this way is incredibly difficult, but I feel I'm making progress.  I couldn't have put a name on it, but I'm using the Composite pattern (or a bastardized similar concept from my imagination) for my new Form class exercise.  The two 'evil' articles from Holub seem interesting, though I'm not done reading them.  The 'Why extends is evil' is surprising, as abstract objects utilize the extends keyword, but I think that my naiveté kicking in, as an abstract pattern is, in some form, an interface.  (Or am I way off base?)

 

Anyhow, great links; I may be picking up that book too.  I need some good, solid reading on patterns and design practices with OOP to help me further grasp it.

Link to comment
Share on other sites

Bastardization of a design pattern is a Good Thing. Keeping it simple and understandable is the key. That's the 'beauty' of programming (to me!)

 

At the core, a purely abstract class is the same as an interface. That is to say:

 

<?php

abstract class A {
  abstract function foo() {}
}

 

Is the same as

 

<?php

interface A {
  public function foo() {}
}

 

The difference is (aside from the fact that an interface MUST NOT contain function definitions,) PHP doesn't allow you to have a class which extends multiple base classes, while you can implement as many interfaces as you'd like. Here's an example

 

<?php

interface A {
  public function foo() {}
}

interface B {
  public function bar() {}
}

//works
class C implements A,B {
  //must define foo(), bar()
}


//doesn't work
class C extends A,B {
    //error
}
?>

 

 

Link to comment
Share on other sites

You're talking about unit testing from a method perspective. You don't unit test methods. That's silly. You unit test the services your object/module provides, which, tests the overall functionality.

 

This is a very good point you are making. Silly as it may be, unit testing focused on methods is very common. I don't see the point of the PHPUnit generator generating test for specific methods. I do use it, but the first thing I do is take out those meaningless test methods. It makes much more sense to write tests that test a functionality of a component. As a result, a test can involve more methods, and methods can be involved in a multitude of tests.

Link to comment
Share on other sites

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.