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.
Related
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();
}
}
How can I assign a function to a method in a class in PHP? I tried the following:
class Something{
public function __construct(){
$functionNames = array('foo', 'bar')
$variable = 'blablabla';
foreach($functionNames as $functionName){
if(method_exists($this, $functionName))
continue;
$this->{$functionName}() = function($params){ //should create the methods "foo" and "bar"
echo $variable; //should echo 'blablabla' (I know that the variable was declared outside this function, but how can I access it anyway?)
}; //the error points to here
}
}
}
But this code gives me this error:
Fatal error: Can't use method return value in write context
Does anyone know how I can assign the anonymous function to the class method, while also still being able to access variables outside that function?
You are doing foreach($functionNames as $functionName){ which means that $functionName is a string, not an array. So, don't use $functionName[0].
method_exists takes 2 parameters. One is the object and the other is the method name. It should be:
method_exists($this, $functionName)
As for creating the function, you don't need () on the left side of the =. It should be:
$this->$functionName = function($params) use($variable){
echo $variable;
};
The use($variable) is needed to tell PHP to use that variable inside the function. That's how closures work in PHP, it's different than other languages.
So, your class should look like:
class Something{
public function __construct(){
$functionNames = array('foo', 'bar');
$variable = 'blablabla';
foreach($functionNames as $functionName){
if(method_exists($this, $functionName)){
continue;
}
$this->$functionName = function($params) use($variable){
echo $variable;
};
}
}
}
Problem here is that in this way of making functions, you are not actually creating a class method, but instead creating a class variable that contains a function.
So, you need to call it like so:
$test = new Something;
$foo = $test->foo;
$foo('abc');
You can't just do $test->foo('abc');.
EDIT: Another thing you can do is use PHP's __call "magic method". This will be ran whenever you do ->funcName(), regardless of whether the method exists or not. Using that method, you can just check to see if the method called was 'foo' or 'bar'. See this example:
class Something{
private $variable;
public function __construct(){
$this->variable = 'blablabla';
}
public function __call($name, $params=array()){
if(method_exists($this, $name)){
// This makes sure methods that *do* exist continue to work
return call_user_func(array($this, $name), $params);
}
else{
$functionNames = array('foo', 'bar');
if(in_array($name, $functionNames)){
// You called ->foo() or ->bar(), so do something
// If you'd like you can call another method in the class
echo $this->variable;
}
}
}
}
With this, now you can do the following:
$test = new Something;
$test->foo('abc'); // Will echo "blablabla"
$user = new User(1);
var_dump($user->ID);
if (empty($user->ID))
echo "empty";
// output string(2) "77" empty
So why is empty() returning true even when $user var is not empty?
The relevant parts of my User class:
class User {
protected $data = null;
public function __construct($userID) {
// sql select
$this->data = $sqlResult;
}
// ...
public function __get($name) {
if (isset($this->data[$name]))
return $this->data[$name];
else
return null;
}
}
UPDATE:
So I updated my User class and added the __isset() method
public function __isset($name) {
if (isset($this->data[$name]) && !empty($this->data[$name]))
return true;
else
return false;
}
This leads me to another problem:
When calling empty() on my not empty var empty($user->ID) it will return false, but when using isset($user->ID) on a declared var which is empty (e.g. $user->ID = '') it will also return false, because isset() will call __isset() inside the class, right?
Is there a way to fix this behaviour?
PHP notes, that I should copy the overloaded property into a local variable, which seems too much paperwork for me ;)
empty() doesn't call __get(). You need to implement __isset().
Quoting from the manual:
Note:
It is not possible to use overloaded properties in other language constructs than isset(). This means if empty() is called on an overloaded property, the overloaded method is not called.
According to the docs, you should overload __isset() for empty to work
I know we can implement PHP's countable interface to determine how the function count() works as well as the Iterator interface for using an object like an array.
Is it possible to implement some kind of interface (or any other way) to change the behavior of empty() on an object?
Essentially, this is what I would like to be able to do:
<?php
class test {
function __empty() {
if (count($this->data) > 0) {
return false;
}
return true;
}
}
Just a nice to have!
No there is not. The iterator behavior and count behaviors have no sense over an object; that's why you can override them. The default behavior of empty() applied to an object has a meaning instead: is it an instance of an object or is it NULL?; that's why you can't override it.
You can instead:
create a method inside the object called isEmpty() and implement it there
create a global function emptyObj() and implement it there
No. PHP does not have it. You can request for such feature on wiki.php.net. In the meantime you can roll your own.
interface Empty_Checkable{
function isempty();
}
class test implements Empty_Checkable{
function isempty() {
if (count($this->data) > 0) {
return false;
}
return true;
}
}
This is not possible on the object directly.
If you need to do it, I would suggest implementing the Countable interface, and then instead of calling empty($foo), call count($foo) == 0...
However, for properties, it is possible using a combination of two magic methods: __isset and __get. For example:
class Foo {
protected $bar = false;
protected $baz = true;
public function __get($key) {
return $this->$key;
}
public function __isset($key) {
return isset($this->$key);
}
}
That results in:
empty($foo->abc); // true, isset returns false
empty($foo->bar); // true, isset returns true, but get returns false value
empty($foo->baz); // false, isset returns true, and get returns a true value
So no, it's not possible with a single method handler, but with the combination of the two magic methods, it works fine...
It is possible to implement Countable
class MyCollection extends stdClass implements Serializable, ArrayAccess, Countable, Iterator, JsonSerializable
{
public function count() {
return count($this->array);
}
}
and use !count() instead of empty() like
if (!count($myList)) {
echo "List is empty!";
}
I don't understand what's going on with this. I need to call Func1 from Func2 and parametr for Func1 should be given inside the object.
class MyClass {
function Func1($a) {
return $a;
}
function Func2() {
echo $this->Func1($a);
}
}
$c = new MyClass();
$c->Func1('parametr'); // prints: 1
$c->Func2();
What about setting the parameter as class variable (property)?
class MyClass {
private $a;
function Func1($a) {
$this->a = $a;
return $a;
}
function Func2() {
echo $this->Func1($this->a);
}
}
This sets the parameter first time you call Func1. Then everytime you call Func2, it uses the parameter. You can also skip passing the parameter like this:
class MyClass {
private $a;
function Func1($a = null) {
if ($a === null) {
return $this->a;
} else {
$this->a = $a;
return $a;
}
}
function Func2() {
echo $this->Func1();
}
}
I.e if you call func1 without any parameter, it uses the stored variable (property), otherwise it uses the given parameter. This can be used in various ways depending on your exact needs.
The instruction:
echo $this->Func1($a);
is wrong: the variable $a is out of the scope of Func2. $a is a parameter of Func1 so is only int he scope of Func1.
You should read more about variable scopes at PHP http://php.net/manual/en/language.variables.scope.php
quick glimpse:
1) you can have global variables. to access those, use keyword global in functions that need access to that
2) you can have local variables, available only within a scope of a function
3) you can pass references to variables, so that variable from one scope is made accessible to other function/scope
4) you can have objects's internal variables of different kind (private, public, protected, static)
I suggest you get familiar with this stuff real well.
As for you code, problem is obvious. In Func2 the $a is local variable, thus when passed to $this->Func1($a), it is undefined. As your example code suggests, you might want to introduce class property private $a, and then use that. e.g.:
class X {
private $a;
function set($val){
$this->a = $val;
}
function get(){
return $this->a;
}
function doSomethingWithA(){
$this->set($this->get() * 2);
}
}