I've been using __set magic method with protected properties to monitor changes so that my classes know if they have something to save. Is there any way to monitor an array type property for changes? I understand that normally you access the array via a reference and functions like array_push won't trigger the __set method, they'll use a reference to the array.
What I want is basically this:
class Skill{ public $Player, $Name, $Level;}
class Player {
protected $Name, /*Other properties*/, $Skills /*Array*/
}
I then do tracking on all of the properties in Player to tell me if the persistence needs updated. (Skill would also have this function, but this shows the basic example). Also, I want to force them to remain synchronized (it's a bidirectional relationship).
Is there any way to do this that allows it to behave like an array (don't want to go through making a class just to synchronize those if I don't have to).
You could extend ArrayObject and proxy append:
class Skills extends ArrayObject
{
public function append($value)
{
// track changes
parent::append($value);
}
}
You could look into something like runkit_function_redifine(), but is it really too cumbersome to make helper methods for what you want? e.g.
class Player
{
private $skills = array();
protected function addSkill($skill)
{
// Do something.
//
$this->skills[] = $skill;
}
}
Or even a wrapper for an array to make it cleaner:
class FancyArray
{
private $content = array();
public function add($value)
{
// Do something.
//
$this->content[] = $value;
}
public function remove($value){ /* blah */ }
public function getContent(){ return $this->content; }
}
class Player
{
protected $skills;
public function __construct()
{
$this->skills = new FancyArray();
$this->skills->add("Ninjitsu");
}
}
Related
I am using PHPStorm.
As an example, I have a simple query class with all sorts of methods to take care of sorting, filtering, ect. It eventually returns an instance of the class passed in the constructor.
class AnimalQuery {
private $classString;
public function __construct(string $classString){
$this->classString = $classString;
}
public function where(string $key, string $val): AnimalQuery {
// Database things would happen here
return $this;
}
public function orderBy(string $val): AnimalQuery {
// Database things would happen here
return $this;
}
public function first() {
// Database things would happen here
return new $this->classString();
}
}
And a class that acts as a model, for example:
class Dog {
public static function query(): AnimalQuery {
return new AnimalQuery(static::class);
}
public function bark() {
echo 'Woof!';
}
}
It can be called like so:
$doggo = Dog::query()->where('name', 'Fido')->orderBy('species')->first();
$doggo->bark();
This does return an instance of Dog. However, PHPStorm of course does not understand that. Is there a way to clue PHPStorm in that the first method returns an instance of the class passed in the constructor of AnimalQuery?
In Typescript I might've used generics for this, but those do not exist in PHP.
I want to implement the strategy design pattern using php:
interface dummy_function {
public function render();
}
class a implements dummy_function {
public function render() {
echo "class a\n";
// I want to acess x_main::dummy like: echo parent::dummy
}
}
class b implements dummy_function {
public function render() {
echo "class b\n";
// I want to acess x_main::dummy like: echo parent::dummy
}
}
class x_main {
public $dummy = "hello world";
public function setX_Function( dummy_function $funcname ) {
$this->x_function = $funcname;
}
public function render() {
$this->x_function->render();
}
}
$test = new x_main();
$test->setX_Function( new a() );
$test->render();
Inside my classes I want to access to some methods and variables defined in the main class. Unfortunatelly "parent" does not work to access contents from the class "x_main" inside the implementation classes.
A way i found is to give $this as parameter to the method "render", like:
class x_main {
[...]
public function render() {
$this->x_function->render( $this );
}
}
class a implements dummy_function {
public function render( $mainclass ) {
echo "class a\n";
echo $mainclass->dummy;
}
}
The next way i testest is to set the variable from the class main direcly into the implemented function, like:
class x_main {
[ ... ]
public function setX_Function( dummy_function $funcname ) {
$this->x_function = $funcname;
$this->x_function->dummy = $dummy;
}
}
class a implements dummy_function {
public function render() {
echo "class a\n";
echo $this->dummy;
}
}
Both solutions work, but I feel a bit confused if that's the best way to implement my desired idea. It looks extremly like a workaround but not like a real OO-programming.
I'm looking forward for your ideas.
Rehashing the comments above:
The two classes aren't related in any way, they certainly don't have a
parent relationship. One object holding an instance of another object
does not mean these two objects are in any sort of relationship with
one another. You simply need to explicitly pass data into your
dummy_function instance; e.g.: public function render(array $data).
Response:
In my first solution I put the whole mainclass as parameter to the
render function. So this is a solution that will work in any case. But
if there is definitivly no relationship between these two objects I
prefer my second solution by setting the parameters directly with
$this->x_function->dummy = $dummy;
Sorry to tell you that you're wrong there. Implicitly setting a property on an object is in no way a defined interface. And I'm not using this word in the sense of PHP's interface keyword, I mean this in the broader sense of specifying how two pieces of code can interact and cooperate.
You've done a good job of specifying and using an interface for the render() function, this greatly decreases code coupling and increases flexibility and testability. Now you're destroying this again by using undefined and brittle property assignments.
You need to make the passing data into render aspect part of your interface specification. For example:
interface dummy_function {
public function render(array $data);
}
Or:
interface dummy_function {
public function setData(array $data);
public function render();
}
Or better:
interface DummyData {
/**
* #return string
*/
public function getDummyData();
}
interface DummyRenderer {
public function render(DummyData $data);
}
With this you're:
explicitly specifying what format your data is in (string)
codifying where render() can get access to such data (it'll receive a DummyData object which has a getDummyData() method)
You don't need to guess what property names are involved or what structure the passed object has.
Here is the data Iterator implementation
//Data Iterator
class DataIterator implements Iterator
{
public $data ;
public function __construct(Data $obj)
{
$this->data = $obj;
}
public function rewind()
{
$this->properties = get_object_vars($this->data);
}
public function valid()
{
if (key($this->properties) === null )
{
return false;
}
return true;
}
public function key()
{
return key($this->properties);
}
public function current()
{
return current($this->properties);
}
public function next()
{
next($this->properties);
}
}
and here is data class
/*Data Class*/
class Data implements IteratorAggregate
{
public $name;
private $age;
protected $address;
public $country;
public $state;
public function __construct($name, $age, $address, $country = 'USA', $state = 'NH')
{
$this->name = $name;
$this->age = $age;
$this->address = $address;
$this->country = $country;
$this->state = $state;
}
function getIterator()
{
return new DataIterator($this);
}
}
And here is the calling part
$data = new Data('Joker', '27', 'California');
foreach($data->getIterator() as $key => $value)
{
echo $key , ' ', $value, '<br>';
}
output
name Joker
country USA
state NH
Notice that the output does not contain my private and protected properties (age, address) output.
How do I tell Iterator to output those as well?
You cannot tell the iterator to output those properties because they are simply not accessible from the outside (i.e. the point where the iterator does get_object_vars($this->data).
There are two ways you could go about doing this:
By having the data object pass the values to the iterator.
Use the reflection API to pull them out by force (verbose, slow!).
But before going ahead with #1 as the preferred option, stop for a moment and ask yourself: why does the iterator expose non-public members of the data object?
Making something private means "You people don't really need to know about this; it may go away in the future, or it may change beyond recognition". If it's something that the outside world cares about, then why is it not public (either directly, or exposed through a public getter)? A rethink of what this iterator's purpose is might be in order.
That said, here's how you would do #1:
class DataIterator implements Iterator
{
public $data;
private $properties;
public function __construct(Data $obj, array $propeties)
{
$this->data = $obj;
$this->properties = $properties;
}
public function rewind()
{
// Arguably horrible trick to refresh the property map without
// demanding that Data exposes a separate API just for this purpose
$newIterator = $this->data->getIterator();
$this->properties = $newIterator->properties;
}
}
class Data implements IteratorAggregate
{
function getIterator()
{
return new DataIterator($this, get_object_vars($this));
}
}
Public, private and protected are access modifiers. They are designed to restrict the accessibility of your class attributes.
Public means that any one can access that attribute, so if someone wants, they can change the value, without that you know it.
Private mean that the attribute is only accessible INSIDE the class,
so nobody can "mess" with those properties from OUTSIDE the class.
Protected is similar like Private, but child classes (classes that
inherit from that class) have access to it.
You are making age and address private, so you are basically saying, nobody is allowed to access these attributes. If you want to access private/protected attributes, you will have to make getters and setters and call these functions, or make the attributes public.
try get_class_vars
$this->properties = get_class_vars(get_class($this->data));
instead of
$this->properties = get_object_vars($this->data);
Imagine two classes which share almost the same exact methods and properties, both extending a parent class, but the differences are minimal.
class fields {
public function __construct() {
global $id;
$this->id = $id++;
}
}
class input extends fields {
public function __construct() {
parent::__construct();
}
public function draw() {
echo '<input>';
}
}
class textarea extends fields {
public function __construct() {
parent::__construct();
}
public function draw() {
echo '<textarea>';
}
}
I'm thinking it would be more efficient to rewrite the textarea class in this psuedo-code fashion:
class textarea extends fields {
public function __construct() {
$this = new input(); // <<------
}
public function draw() {
echo '<textarea>';
}
}
Basically, I'm unsure how this would best be done so that the class acts like the class from the first example.
In essence, I would like to do the following using OOP, but be able to use the object as it can be in the first example above (be able to call the possibly overloaded methods, have different properties, etc.):
function a() {echo '123';}
function b() {a();}
I have just copied the entire class and modify a few lines, but I feel it is wasteful.
Final Answer
Thanks to those people, here is the combined answer with example calls:
abstract class fields {
private static $masterid = 0;
public function __construct() {
$this->id = self::$masterid++;
}
}
class input extends fields {
public $data;
public function __construct($new = '') {
parent::__construct();
if ($new) $this->data = $new;
else $this->data = 'Hello';
}
public function draw() {
echo '<input>'.$this->export().'</input>';
}
public function export() {
return 'ID '.$this->id.' = '.$this->data;
}
}
class textarea extends input {
public function __construct($new = '') {
parent::__construct($new);
}
public function draw() {
echo '<textarea>'.$this->export().'</textarea>';
}
}
$a = new textarea();
$a->draw();
$a = new textarea('World');
$a->draw();
$a = new input('!');
$a->draw();
//Outputs:
// <textarea>ID 0 = Hello</textarea>
// <textarea>ID 1 = World</textarea>
// <input>ID 2 = !</input>
Make the fields class an abstract class, and like Darren suggested, make the 'draw' method a function of the fields class.
Now heres the trick, you want the input class to extend fields, but override the draw method. This will allow you to customize the functionality of that method, and you can still call the parent variation from within it.
Finally, since the textarea class is going to have many similarities to the input class, make textarea extend input. Thereby inheriting the properties and methods of both fields and input.
Make the "fields" class have a draw method:
public function draw($msg) {
echo $msg;
}
Then in the textarea or input class put:
parent::draw("<input>");
This cuts down on the number of methods you have, and can call one method for both types of field.
Also in your "fields" class, change the id code to be like this:
public $id
public function __construct($id) {
$this->id = $id;
}
Then in the subclass:
parent::__construct(1); //Or whatever ID you want
The way you have it, ID is the same value every time you set it, which will result in every subclass of fields having the same id. This way each subclass will have a seperate ID.
Also because I'm nice, here's it all put together:
public class field {
$id;
public __construct($id) {
$this->id = $id;
}
public function draw($msg) {
echo $msg;
}
}
public class input extends field {
public __construct() {
parent::__construct(1);
parent::draw("<input>");
}
}
public class textarea extends field {
public __construct() {
parent::__construct(2);
parent::draw("<textarea>");
}
}
That's how I'd put it together from what you've said. I may have mistaken what you were asking for though. Can you tell I'm primarily a Java programmer from that?
It's not exactly clear what you want to do. For the example you've given, I think the structure is OK, but you should make a few changes, particularly with the constructor. I think the constructor should be abstract, with an abstract method draw().
abstract class fields {
// Use a static member to keep track of id's instead of making it global
private static $id = 0;
// Use an instance variable to keep track of a particular instance's id
private $myId;
public function __construct() {
// Increment the static ID & assign it to the instance id.
$this->myId = self::$id++;
}
// Provide a public getter, so that the ID can't be changed
// externally to this class
public function getId() {
return $this->myId;
}
public abstract draw(); // Make sure all sub classes implement a draw() method.
}
class input extends fields {
// Don't need to call the parent constructor if you're not adding anything
// else. It will be called automatically.
public function draw() {
echo '<input>';
}
}
class textarea extends fields {
public function draw() {
echo '<textarea>';
}
}
So, I'm having trouble with some php OO stuff. I think the code will explain it best:
class foo {
$someprop;
public function __construct($id){
$this->populate($id);
}
private function populate($id){
global $db;
// obviously not the call, but to illustrate the point:
$items = $db->get_from_var1_by_var2(get_class($this),$id);
while(list($k,$v) = each($items)){
$this->setVar($k,$v);
}
}
private function setVar($k,$v){
// filter stuff, like convert JSON to arrays and such.
$this->$k = $v;
}
}
class bar extends foo {
$otherprop;
public function __construct($id){
parent::__construct($id);
}
private function setVar($k,$v){
// different filters than parent.
$this->$k = $v;
}
}
Now, assuming my foo table has someprop in it, and my bar table has otherprop in it, this should set the vars on my object when I pass in an ID.
But, for some reason, foo works perfectly, but bar doesn't set anything.
My assumption is that it is falling apart on the $this->setVar() call, and calling the wrong setVar, but if get_class($this) is working (which it is), shouldn't $this be bar, and by association, setVar() be the $bar method?
Anyone see something I'm missing/doing wrong?
You cannot override private methods in subclasses. A private method is known to the implementing class ONLY, not even subclasses.
You CAN do this though:
class foo {
$someprop;
public function __construct($id){
$this->populate($id);
}
private function populate($id){
global $db;
// obviously not the call, but to illustrate the point:
$items = $db->get_from_var1_by_var2(get_class($this),$id);
while(list($k,$v) = each($items)){
$this->setVar($k,$v);
}
}
protected function setVar($k,$v){
// filter stuff, like convert JSON to arrays and such.
$this->$k = $v;
}
}
class bar extends foo {
$otherprop;
public function __construct($id){
parent::__construct($id);
}
protected function setVar($k,$v){
// different filters than parent.
$this->$k = $v;
}
}