How to implement __isset() magic method in PHP? - php

I'm trying to make functions like empty() and isset() work with data returned by methods.
What I have so far:
abstract class FooBase{
public function __isset($name){
$getter = 'get'.ucfirst($name);
if(method_exists($this, $getter))
return isset($this->$getter()); // not working :(
// Fatal error: Can't use method return value in write context
}
public function __get($name){
$getter = 'get'.ucfirst($name);
if(method_exists($this, $getter))
return $this->$getter();
}
public function __set($name, $value){
$setter = 'set'.ucfirst($name);
if(method_exists($this, $setter))
return $this->$setter($value);
}
public function __call($name, $arguments){
$caller = 'call'.ucfirst($name);
if(method_exists($this, $caller)) return $this->$caller($arguments);
}
}
the usage:
class Foo extends FooBase{
private $my_stuff;
public function getStuff(){
return $this->my_stuff;
}
public function setStuff($stuff){
$this->my_stuff = $stuff;
}
}
$foo = new Foo();
if(empty($foo->stuff)) echo "empty() works! \n"; else "empty() doesn't work:( \n";
$foo->stuff = 'something';
if(empty($foo->stuff)) echo "empty() doesn't work:( \n"; else "empty() works! \n";
http://codepad.org/QuPNLYXP
How can I make it so empty/isset return true/false if:
my_stuff above is not set, or has a empty or zero value in case of empty()
the method doesn't exist (not sure if neeed, because I think you get a fatal error anyway)
?

public function __isset($name){
$getter = 'get'.ucfirst($name);
return method_exists($this, $getter) && !is_null($this->$getter());
}
This check whether or not $getter() exists (if it does not exist, it's assumed that the property also does not exist) and returns a non-null value. So NULL will cause it to return false, as you would expect after reading the php manual for isset().

A bit more option not to depend on getter
public function __isset($name)
{
$getter = 'get' . ucfirst($name);
if (method_exists($this, $getter)) {
return !is_null($this->$getter());
} else {
return isset($this->$name);
}
}

Your code returns error because of these lines:
if(method_exists($this, $getter))
return isset($this->$getter());
You can just replace it with:
if (!method_exists($this), $getter) {
return false; // method does not exist, assume no property
}
$getter_result = $this->$getter();
return isset($getter_result);
and it will return false if the getter is not defined or it returns NULL. I propose you should better think of the way you determine some property is set or not.
The above code is also assuming that you are creating getters for all of your properties, thus when there is no getter, the property is assumed as not set.
Also, why are you using getters? They seem to be some overkill here.

Related

empty() not catching a string? [duplicate]

I have a "getter" method like
function getStuff($stuff){
return 'something';
}
if I check it with empty($this->stuff), I always get FALSE, but I know $this->stuff returns data, because it works with echo.
and if I check it with !isset($this->stuff) I get the correct value and the condition is never executed...
here's the test code:
class FooBase{
public function __get($name){
$getter = 'get'.ucfirst($name);
if(method_exists($this, $getter)) return $this->$getter();
throw new Exception("Property {$getter} is not defined.");
}
}
class Foo extends FooBase{
private $my_stuff;
public function getStuff(){
if(!$this->my_stuff) $this->my_stuff = 'whatever';
return $this->my_stuff;
}
}
$foo = new Foo();
echo $foo->stuff;
if(empty($foo->stuff)) echo 'but its not empty:(';
if($foo->stuff) echo 'see?';
empty() will call __isset() first, and only if it returns true will it call __get().
Implement __isset() and make it return true for every magic property that you support.
function __isset($name)
{
$getter = 'get' . ucfirst($name);
return method_exists($this, $getter);
}
Magic getters are not called when checking with empty. The value really does not exist, so empty returns true. You will need to implement __isset as well to make that work correctly.
__isset() is triggered by calling isset() or empty() on inaccessible properties.
http://www.php.net/manual/en/language.oop5.overloading.php#language.oop5.overloading.members
PHP's magic get method is named __get(). $this->stuff will not call getStuff(). Try this:
public function __get($property) {
if ($property == 'stuff') {
return $this->getStuff();
}
}

Return variable class name from php factory

I've got a factory that I want to return a ::class from. However, the factory could potentially return a couple dozen different types (determined by the type of object passed into the factory), named TypeOneObject, TypeTwoObject etc. Is it possible to return the class using a variable, something like this?
$type = $myrequiredclass->getType();
return $type."Object"::class; // wanting TypeOneObject::class
It seems like no matter how I construct this return statement I always get PHP Parse error: syntax error, unexpected '::'
I know it'd be easy enough to do with a big if/then or switch but I'd like to avoid that.
Here's a more fleshed out scenario:
class TypeOneObject
{
public static function whoAreYou()
{
return 'Type One Object!';
}
}
class MyRequiredClass
{
public function getType()
{
return 'TypeOne';
}
}
class MyFactory
{
public static function getFactoryObject(MyRequiredClass $class)
{
$type = $class->getType()."Object";
return $type::class;
}
}
$object = MyFactory::getFactoryObject(new MyRequiredClass());
$object::whoAreYou();
The best way to get the class name from the $type instance is to use php get_class_methods function. This will get us all the methods inside the class instance. from there we can filter and use call_user_func to call the method and get the right values.
class TypeOneObject
{
public static function whoAreYou()
{
return 'Type One Object!';
}
}
class MyRequiredClass
{
public function getType()
{
return 'TypeOne';
}
}
class MyFactory
{
public static function getFactoryObject(MyRequiredClass $class)
{
$methods = get_class_methods($class);
$method = array_filter($methods, function($method) {
return $method == 'getType';
});
$class = new $class();
$method = $method[0];
$methodName = call_user_func([$class, $method]);
$objectName = sprintf('%sObject', $methodName);
return new $objectName;
}
}
$object = MyFactory::getFactoryObject(new MyRequiredClass());
echo $object::whoAreYou();
Output
Type One Object!

Php Magic Methods and Empty

Having the following code
class test {
private $name;
public function __get($name){
return $name;
}
public function __set($name,$value){
$this->name = $value;
}
}
$obj = new test();
$obj->a = 2;
if (!empty($obj->a)) {
echo 'not empty';
}
This is calling __isset. But this is not being defined so it always return empty. What is the best way to check for a non empty property?
Update :changing the class is not a solution because it's a 3th party component and it has to remain intact.
If you can't change the class, I think the only possible workaround is using a temporary variable.
$obj->a = 2;
$test = $obj->a;
if (!empty($test)) {
echo 'not empty';
}
I know I am very late to the party here, however I am posting this for the edificationof any who may stumble across this question.
Firstly, I believe that the test class is wrong and if that is really what the 3rd party component does, I would chuck it out because it's rubbish. Do you really want all property names to map internally to the single property 'name', and thereby overwrite each other? Do you really want all property names to be returned as the property value? The code should look like this:
class test {
public function __get($name){
return $this->$name;
}
public function __set($name,$value){
$this->$name = $value;
}
}
Secondly, you can change the class, even if it has to remain intact. That's the point of inheritance. This is the open-closed principle. If the functions are incorrect, simply extend test like this to correct them:
class test {
private $name;
public function __get($name){
return $name;
}
public function __set($name,$value){
$this->name = $value;
}
}
class my_test extends test
{
public function __get($name)
{
return $this->$name;
}
public function __set($name,$value){
$this->$name = $value;
}
}
You shouldn't need to define __isset() as the corrected code will do what it is meant to do, but if you did you could do that here too.
Now the following will do what it is supposed to do (note the change of class name):
$obj = new my_test();
$obj->a = 2;
if (!empty($obj->a)) {
echo 'not empty';
}
change
public function __set($name,$value){
$this->name = $value;
}
To
public function __set($name,$value){
$this->$name = $value;
}
And then try
It does not make sense when used with anything other than the variable; ie empty (addslashes ($ name)) does not make sense, since it will be checked by anything other than a variable as a variable with a value of FALSE.
In your case, you should use the type conversion:
if ((bool)$obj->a) {
echo 'not empty';
}

Using php's magic function inside another function does not work

I want to use magic function __set() and __get() for storing SQL data inside a php5 class and I get some strange issue using them inside a function:
Works:
if (!isset($this->sPrimaryKey) || !isset($this->sTable))
return false;
$id = $this->{$this->sPrimaryKey};
if (empty($id))
return false;
echo 'yaay!';
Does not work:
if (!isset($this->sPrimaryKey) || !isset($this->sTable))
return false;
if (empty($this->{$this->sPrimaryKey}))
return false;
echo 'yaay!';
would this be a php bug?
empty() first* calls the __isset() method and only if it returns true the __get() method. i.e. your class has to implement __isset() as well.
E.g.
<?php
class Foo {
public function __isset($name) {
echo "Foo:__isset($name) invoked\n";
return 'bar'===$name;
}
public function __get($name) {
echo "Foo:__get($name) invoked\n";
return 'lalala';
}
}
$foo = new Foo;
var_dump(empty($foo->dummy));
var_dump(empty($foo->bar));
prints
Foo:__isset(dummy) invoked
bool(true)
Foo:__isset(bar) invoked
Foo:__get(bar) invoked
bool(false)
* edit: if it can't "directly" find an accessible property in the object's property hashtable.

Magic Method __set() on a Instantiated Object

Ok i have a problem, sorry if i cant explaint it clear but the code speaks for its self.
i have a class which generates objects from a given class name;
Say we say the class is Modules:
public function name($name)
{
$this->includeModule($name);
try
{
$module = new ReflectionClass($name);
$instance = $module->isInstantiable() ? $module->newInstance() : "Err";
$this->addDelegate($instance);
}
catch(Exception $e)
{
Modules::Name("Logger")->log($e->getMessage());
}
return $this;
}
The AddDelegate Method:
protected function addDelegate($delegate)
{
$this->aDelegates[] = $delegate;
}
The __call Method
public function __call($methodName, $parameters)
{
$delegated = false;
foreach ($this->aDelegates as $delegate)
{
if(class_exists(get_class($delegate)))
{
if(method_exists($delegate,$methodName))
{
$method = new ReflectionMethod(get_class($delegate), $methodName);
$function = array($delegate, $methodName);
return call_user_func_array($function, $parameters);
}
}
}
The __get Method
public function __get($property)
{
foreach($this->aDelegates as $delegate)
{
if ($delegate->$property !== false)
{
return $delegate->$property;
}
}
}
All this works fine expect the function __set
public function __set($property,$value)
{
//print_r($this->aDelegates);
foreach($this->aDelegates as $k=>$delegate)
{
//print_r($k);
//print_r($delegate);
if (property_exists($delegate, $property))
{
$delegate->$property = $value;
}
}
//$this->addDelegate($delegate);
print_r($this->aDelegates);
}
class tester
{
public function __set($name,$value)
{
self::$module->name(self::$name)->__set($name,$value);
}
}
Module::test("logger")->log("test"); // this logs, it works
echo Module::test("logger")->path; //prints /home/bla/test/ this is also correct
But i cant set any value to class log like this
Module::tester("logger")->path ="/home/bla/test/log/";
The path property of class logger is public so its not a problem of protected or private property access.
How can i solve this issue? I hope i could explain my problem clear.
EDIT:
A simple demonstration
Modules::Name("XML_Helper")->xmlVersion ="Hello"; // default is 333
$a = Modules::Name("XML_Helper")->xmlVersion; // now $a should contain "Hello"
echo $a; // prints 333
What i need is
Modules::Name("XML_Helper")->xmlVersion ="Hello"; // default is 333
$a = Modules::Name("XML_Helper")->xmlVersion; // now $a should contain "Hello"
echo $a; // prints Hello
I realise you already said that path is public, but it's still worth mentioning: If you're using PHP 5.3.0+, note this quirk of property_exists():
5.3.0 | This function checks the existence of a property independent of
accessibility
In other words, if you check if (property_exists($delegate, $property)), you have no guarantee you have access to $delegate->$property for writing (or reading, for that matter, but you are trying to write).
As for actual troubleshooting: You could try checking if your if (property_exists($delegate, $property)) statement actually executes. If it doesn't, check the case of $property.
Sidenote: It's fairly hard to read the code you posted up, which makes it a bit of a pain to troubleshoot. Could you edit your post and indent it properly?
The path property of class logger is public so its not a problem of
protected or private property access.
That's your problem. From the docs:
__set() is run when writing data to inaccessible properties.
That suggests that __set() is not called for public properties.

Categories