Does PHP have a method of having auto-generated class variables? I think I've seen something like this before but I'm not certain.
public class TestClass {
private $data = array();
public function TestClass() {
$this->data['firstValue'] = "cheese";
}
}
The $this->data array is always an associative array but they keys change from class to class. Is there any viable way to access $this->data['firstValue'] from $this->firstValue without having to define the link?
And if it is, are there any downsides to it?
Or is there a static method of defining the link in a way which won't explode if the $this->data array doesn't contain that key?
See here: http://www.php.net/manual/en/language.oop5.overloading.php
What you want is the "__get" method. There is an example for what you need on the link.
Use the PHP5 "magic" __get() method. It would work like so:
public class TestClass {
private $data = array();
// Since you're using PHP5, you should be using PHP5 style constructors.
public function __construct() {
$this->data['firstValue'] = "cheese";
}
/**
* This is the magic get function. Any class variable you try to access from
* outside the class that is not public will go through this method. The variable
* name will be passed in to the $param parameter. For this example, all
* will be retrieved from the private $data array. If the variable doesn't exist
* in the array, then the method will return null.
*
* #param string $param Class variable name
*
* #return mixed
*/
public function __get($param) {
if (isset($this->data[$param])) {
return $this->data[$param];
} else {
return null;
}
}
/**
* This is the "magic" isset method. It is very important to implement this
* method when using __get to change or retrieve data members from private or
* protected members. If it is not implemented, code that checks to see if a
* particular variable has been set will fail even though you'll be able to
* retrieve a value for that variable.
*
* #param string $param Variable name to check
*
* #return boolean
*/
public function __isset($param) {
return isset($this->data[$param]);
}
/**
* This method is required if you want to be able to set variables from outside
* your class without providing explicit setter options. Similar to accessing
* a variable using $foo = $object->firstValue, this method allows you to set
* the value of a variable (any variable in this case, but it can be limited
* by modifying this method) by doing something like:
* $this->secondValue = 'foo';
*
* #param string $param Class variable name to set
* #param mixed $value Value to set
*
* #return null
*/
public function __set($param, $value) {
$this->data[$param] = $value;
}
}
Using the magic __get, __set, and __isset constructors will allow you to control how you want variables to be set on a class while still storing all the values in a single array.
Hope this helps :)
Related
I am geting a headache with php "Strict Standards" notices in a class:
Parent class:
class View {
/**
*
* #param string $viewFolder
* #param string $viewBasename
* #return boolean
*/
public static function exists($viewFolder, $viewBasename){
$exists = false;
if(\is_string($viewFolder) && \is_string($viewBasename)){
$exists = \is_file(\APPLICATION_PATH."View/$viewFolder/$viewBasename.phtml");
}
return $exists;
}
/**
*
* #param string $viewFolder
* #param string $viewBasename
* #param array $viewVariables
*/
public static function load($viewFolder, $viewBasename,array $viewVariables = []){
extract($viewVariables);
require \APPLICATION_PATH."View/$viewFolder/$viewBasename.phtml";
}
}
Child class:
class Layout extends View{
/**
*
* #param string $viewBasename
*/
public static function exists($viewBasename) {
return parent::exists('_layout', $viewBasename);
}
/**
*
* #param string $viewBasename
* #param array $viewVariables
*/
public static function load($viewBasename, array $viewVariables = array()) {
parent::load('_layout', $viewBasename, $viewVariables);
}
}
I've read this topic and now its clear that the reason are those missing parameters in the child class methods.
Declaration of Methods should be Compatible with Parent Methods in PHP
Is there a way of getting rid of these notices without disabling error reporting, or maybe a better approach of doing this?
Thanks in advance.
The better approach is to write your classes in a clean and sensible way. In terms of OOP practice, your child classes that need to extend the parent's methods should redefine them in the same format (hence your warning from PHP).
In your example, your general workflow for the exists() method implementation appears to be the following:
Parent class has an exists method with a folder and a filename
Child class is cutting corners, because it knows its folder already, and only accepts a filename
Child class passes a predefined variable to the parent method
If you look at this objectively, your goal is that the view should be able to call the exists() method on a Layout class and only pass the one parameter, so then you ask "how can I remove the requirement to pass the folder?" Here are a couple of options:
1: Pass the folder name in as the second argument, and making it optional in the Layout (child) class's implementation:
# Class: Layout
/**
* #param string $viewBasename
* #param string $viewFolder
*/
public static function exists($viewBasename, $viewFolder = '_layout') {
return parent::exists($viewBasename, $viewFolder);
}
# Class: View
public static function exists($viewBasename, $viewFolder) {
// essentially you swap around the order of the params
}
2: Don't pass in the folder at all, but use a class property in the child and take advantage of late static bindings:
# Class: Layout
/**
* Define the folder for your layouts
* #var string
*/
const VIEW_FOLDER = '_layout';
The exists() implementation stays the same as in your example currently.
# Class: View
public static function exists($viewBasename) {
// Get your folder from a child instead of an argument
$viewFolder = static::VIEW_FOLDER;
$exists = false;
if(\is_string($viewFolder) && \is_string($viewBasename)){
$exists = \is_file(\APPLICATION_PATH."View/$viewFolder/$viewBasename.phtml");
}
return $exists;
}
A note here, you could also use a function in place of the constant, either abstract in the View class or not, e.g.:
# Class: View
abstract class View {
/**
* This method should be defined in children to provide the layout name.
* Using an abstract method would ensure that it is defined by children,
* however if View is going to be used on its own then do not use this approach.
* #return string The view's folder name
*/
abstract protected static function getViewFolder();
public static function exists($viewBasename) {
// Get view folder from the children (same as the constant example)
$viewFolder = static::getViewFolder();
// ...
}
}
# Class: Layout
class Layout extends View {
protected static function getViewFolder() {
return '_layout';
}
public static function exists($viewBasename) {
return parent::exists($viewBasename);
}
}
To be honest, the constant option is a little shorter and they both essentially do the same thing, other than if you use a function instead of a constant you would be able to define manipulation logic if required.
If I were you, I'd use a class constant for the view folder and take it out as an argument. You'd then implement the static::VIEW_FOLDER in place of the argument passed into load and exists.
In my php application I have been comparing objects with the usual equality comparison operator, e.g.:
if ($objectA == $objectB) { ... }
Recently I implemented proxies (for objects which are expensive to load) however this means the equality operator no longer works. Is there a simple way around this? One that doesn't rely on reflection?
For the moment, I have resorted to testing the unique identifier of each object, e.g.
if ($objectA->getId() == $objectB->getId) { ... }
But this has two problems: 1) I need to refactor all existing code, and 2) in the future I may need to compare objects which are value objects (not entities).
I'm not hopeful of an easy solution since I think it would require a new magic method...
Here's my AbstractProxy class. Any help appreciated...
abstract class KOOP_Base_AbstractProxy
implements KOOP_Base_iDomain
{
use KOOP_Trait_Helper_Helper;
/**
* #var integer Object identifier
*/
protected $_id = null;
/**
* #var KOOP_Base_AbstractMapper
*/
protected $_mapper = null;
/**
* #var KOOP_Base_AbstractDomain Actual object
*/
protected $_subject = null;
/**
* Store object id for lazy loading
*
* #param integer $id Object identifier
* #param string $mapper Mapper by which to retrieve object
*/
public function __construct($id, $mapper)
{
$this->_id = $id;
$this->_mapper = $mapper;
}
/**
* Get subject
*
* #return KOOP_Base_AbstractDomain
*/
protected function getSubject()
{
if (!$this->_subject) {
$this->_subject = $this->getMapper($this->_mapper)->find($this->_id);
}
return $this->_subject;
}
/**
* Get property
*
* #param string $property
* #return mixed
*/
public function __get($property)
{
return $this->getSubject()->$property;
}
/**
* Set property
*
* #param string $property
* #param mixed $value
* #return void
*/
public function __set($property, $value)
{
$this->getSubject()->$property = $value;
}
/**
* Is property set?
*
* #param $property
* #return boolean
*/
public function __isset($property)
{
return isset($this->getSubject()->$property);
}
/**
* Unset property
*
* #param string $property
* #return mixed
*/
public function __unset($property)
{
unset($this->getSubject()->$property);
}
/**
* Call method
*
* #param string $method Method to call
* #param array $params Parameters to pass
* #return mixed
*/
public function __call($method, array $params)
{
return call_user_func_array(array($this->getSubject(), $method), $params);
}
/**
* Get id
*
* Saves having to retrieve the entire object when only the ID is required.
*/
public function getId()
{
return $this->_id;
}
}
Proxies do break object equality, and there's no utterly clean way to fix this. In a fully object oriented language you would handle this by operator overloading (which I don't recommend) or implementing a custom .equals() function (as in Java). Sadly, PHP simply does not support object orientation at this level, so you will have some decisions to make.
1) I would prefer to have your proxy class provide an equals() function which takes as input a reference to the object you want to test against and compares it to the proxied object - which shouldn't be much more 'expensive' than it was to not use a proxy at all. Example in pseudo-PHP code (my apologies if my reference syntax is off, it's been a while):
public function equals (&$toCompare)
{
if ($_subject == $toCompare)
{
return true;
}
else
{
return false;
}
}
The downside is simple: you have to refactor your code that involves this proxied object, and you have to remember that "==" does not work on this proxied object type while you are working. If you don't deal with these objects much, or if you deal with them all the time, this is fine. If you deal with them regularly but intermittently, or if others must work with them on occasion, then this will cause bugs when you/they forget about this equality problem.
2) Use an Operator Overloading extension to the language. I haven't done this, I don't know if it works, and it might be a nightmare. I include it for theoretical completeness.
Personally, I think I'd just hack it with the pseudo-Java approach call it a day, as I think it would actually work and require nothing more than using the function correctly (and remembering to use it in the first place).
I have a method that accepts a callback as a parameter. I would like to provide a signature in the PHPDoc for the class method that outlines the parameters for the callback function to be passed to that method so that my IDE (PHPStorm) can produce valid type hints for functions that are passed to my method, or at least someone looking at the code can determine the signature of the callback they're intended to provide.
For example:
class Foo {
public $items = [];
/**
* #param Callable(
* #param ArrayObject $items The list of items that bar() will return
* ) $baz A callback to receive the items
**/
public function bar(Callable $baz) {
$items = new ArrayObject($this->items);
$baz($items);
}
}
The method bar has one parameter, $baz, which is a callback function. Any function passed as an parameter to bar() must accept an ArrayObject as its only parameter.
Ideally, it should be possible to include multiple parameters for the Callable, just like for any other method.
When I write the following code:
$foo = new Foo();
$foo->bar(function(
...I should then receive a parameter list that correctly hints the type (ArrayObject) of the accepted parameter for this function call.
Is such a thing possible? Does PHPStorm or another IDE support it? Is there a recommended/standard way of documenting this even if there is no IDE support?
PHP 7+:
Using an interface for the callable combined with anonymous classes will do the trick. It's not very handy and leads to bit too complex code for the class-consumer, but currently it's the best solution in terms of static code-analysis.
/**
* Interface MyCallableInterface
*/
interface MyCallableInterface{
/**
* #param Bar $bar
*
* #return Bar
*/
public function __invoke(Bar $bar): Bar;
}
/**
* Class Bar
*/
class Bar{
/**
* #var mixed
*/
public $data = null;
}
/**
* Class Foo
*/
class Foo{
/**
* #var Bar
*/
private $bar = null;
/**
* #param MyCallableInterface $fn
*
* #return Foo
*/
public function fooBar(MyCallableInterface $fn): Foo{
$this->bar = $fn(new Bar);
return $this;
}
}
/**
* Usage
*/
(new Foo)->fooBar(new class implements MyCallableInterface{
public function __invoke(Bar $bar): Bar{
$bar->data = [1, 2, 3];
return $bar;
}
});
If you're using PhpStorm it will even auto-generate the __invoke-Method's signature & body within the anonymous class.
I overcame this problem by defining a static function within the class using the callable. That function has its own doc-block and I just refer to it in the method that's requiring my callable using PHPDoc's #see tag.
class Foo
{
/**
* Description of the "bar" callable. Used by {#see baz()}.
*
* #param int $index A 1-based integer.
* #param string $name A non-empty string.
* #return bool
* #see baz()
* #throws \Exception This is a prototype; not meant to be called directly.
*/
public static barCallable($index, $name)
{
throw new \Exception("barCallable prototype called");
}
/**
* Description of the baz() method, using a {#see barCallable()}.
*
* #param callable $bar A non-null {#see barCallable()}.
* #see barCallable()
*/
public function baz(callable $bar)
{
// ...
call_user_func($bar, 1, true);
// ...
}
}
This works well in PhpStorm 10. the Quick Documentation allows to navigate from the method documentation to the prototype documentation easily.
I make my prototype function throw an exception to make it clear it's not meant to be called. I could use a protected or private scope, but then PHPDoc would not always pick the doc-block for documentation generation.
Unfortunately PhpStorm can't track usage of callbacks. It doesn't provide Parameter Info either when using the method requiring a callback, but the callback is at least formally documented.
This approach also has the added benefit of validating a callback definition from the reflection of the prototype at run-time.
It is not possible in PhpStorm by now. I can't even think of other solution which do relatively the same by other means.
After an upgrade of Codeigniter I get this message:
Cannot access protected property MY_Loader::$_ci_cached_vars
I know that this property is now protected so what can I do here?
extract($CI->load->_ci_cached_vars); // extract cached variables
I don't know how to use the get_var method now, because the property is protected
this is get_var method
/**
* Get Variable
*
* Check if a variable is set and retrieve it.
*
* #param array
* #return void
*/
public function get_var($key)
{
return isset($this->_ci_cached_vars[$key]) ? $this->_ci_cached_vars[$key] : NULL;
}
How to refactor this?
extract($CI->load->get_var($key));
Is it possible to make all function's vars global without typing all of them like global $a, $b, $c...?
Try creating a Static object within your application and assigning variables to that scope, Like so!
<?php
/*
* Core class thats used to store objects of ease of access within difficult scopes
*/
class Registry
{
/*
* #var array Holds all the main objects in an array a greater scope access
* #access private
*/
private static $objects = array();
/**
* Add's an object into the the global
* #param string $name
* #param string $object
* #return bool
*/
public static function add($name,$object)
{
self::$objects[$name] = $object;
return true;
}
/*
* Get's an object out of the registry
* #param string $name
* #return object on success, false on failure
*/
public static function get($name)
{ if(isset(self::$objects[$name]))
{
return self::$objects[$name];
}
return false;
}
/**
* Removes an object out of Registry (Hardly used)
* #param string $name
* #return bool
*/
static function remove($name)
{
unset(self::$objects[$name]);
return true;
}
/**
* Checks if an object is stored within the registry
* #param string $name
* #return bool
*/
static function is_set($name)
{
return isset(self::$objects[$name]);
}
}
?>
Considering this file is one of the first files included you can set any object/array/variable/resource/ etc into this scope.
So lets say you have just made a DB Connection, this is hwo you use it
...
$database = new PDO($dns);
Registry::add('Database',$database);
$DBConfig = Registry::get('Database')->query('SELECT * FROM config_table')->fetchAll(PDO::FETCH_CLASS);
Registry::add('Config',$DBConfig);
No anywhere else within your script you can add or retrieve items.
with Registry::get('ITEM_NEEDED');
This will work in methods functions etc.
Perfect example
function insertItem($keys,$values)
{
Registry::get('Database')->query('INSERT INTO items ('.implode(',',$keys).') VALUES ('.implode(',',$values).')');
}
Hope it helps
No. That'd be an awful thing to do anyways.
You can pass them as arguments and then you won't need the global keyword:
function(&$a, &$b, &$c)
{
// your code........
}
You can always use $GLOBALS["var"] instead of $var. Of course, if you need this, you're most likely doing it wrong.