I am not aware of the technical term how to call it.
I have a base class which has $content property holding entire page array elements. For some constraint, I cannot extends the base class
Base Class
/**
* This is the main class
*
* Class base
*/
class base {
/**
* Holding entire page data in nested array. Such as nav item, posts, metadata etc. with the respective key => []
* Example:
* $this->content = [
* 'head' => [ // some body elements such as meta tags, title etc],
* 'body' => [
* ]
* ]
*
* #var array
*/
public $content = [];
/* This is just for an example of array holding values */
public function some_methods() {
// various method to process the $content and return the element
$this->content = [
'head' => [ /* some body elements such as meta tags, title etc */ ],
'body' => [
'footer' => [ /* header elements */ ],
'nav' => [ /* nav items */ ],
'article' => [
'text' => [ /* text and title */],
'metadata' => [ /* metadata */]
],
'footer' => [ /* footer elements */ ]
]
];
}
public function load_navs( $navs ) {
$one = new one();
$one->hello($navs);
// OR
// some methods has loops in the `base` class
foreach ( $navs as $key => $value ) {
// whatever..
$one->hello($key, $value);
}
}
public function load_footer( $footer ) {
$two = new two();
$two->widget($footer);
}
}
What I want to do is to make some more classes to customize some logic of the base class methods. For that to get a value I need to use $content of the base class into the another class. They the methods from the newly created classes I will use in the base class methods
One
/**
* Class to customize or override base class logic
* Class one
*/
class one {
// to do some process I need to access $content from the `base` class. For example
public function hello($navs){
// here something to check from `nav`
// this method I will use in base class
if ($this->content['nav']['item']['selected']){
// do the stuff
}
}
}
Two
/**
* Class to customize or override base class logic
* Class two
*/
class two {
// to do some process I need to access $content from the `base` class. For example
public function hello($footer){
// here something to check from `footer`
// this method I will use in base class
if ($this->content['footer']['widget']['type'] == 'foo'){
// do the stuff
}
}
}
If you can't extends your class, you can use static attribute. An static attribute is an attribute link to the class and not to an instantiated object.
Look at this link for details. I copy past the important informations
Declaring class properties or methods as static makes them accessible
without needing an instantiation of the class.
So you can declare static $content in your base Class and use it like : base::$content;
class Base {
public static $content = [];
}
class One {
function foo () {
var_dump(Base::$content);
}
}
$foo = new One();
$foo->foo();
//show :
array (size=0)
empty
And by convention, your name Classe need to upperCase (Base, One, Two)
EDIT : No Static, No Extends.
/*
if you can't extends AND you can't use static, you can instancied Base object in you One Class.
Like Tobias say in this comment, if you want change the $content for both Base and One, you need pass the var via reference.
Look at this first example, the base->$content is empty before and after One work. And look at the 2nd example : I get the base content
via reference and the content change in both case.
My english is probably too poor so I suggest to you to read the doc for reference.
*/
class Base {
public $content = [];
function displayContent() {
var_dump($this->content);
}
}
class One {
function displayContent() {
$base = new Base();
$base->content = 'toto';
var_dump($base->content);
}
}
$base = new Base();
$one = new One();
$one->displayContent();
$base->displayContent();
// $one->content == 'toto'; but $base->content still empty.
Now: with reference :
class Base {
public $content = [];
function displayContent() {
var_dump($this->content);
}
}
class One {
public $content;
function displayContent() {
var_dump($this->content);
}
}
$base = new Base();
$one = new One();
$one->content = &$base->content;
$one->content = 'toto';
$one->displayContent();
$base->displayContent();
// NOW, $one->content == 'toto'; and $base->content = 'toto'
Please use below mentioned lines while inherits property of base class.
class One extends Base
class Two extends Base
Than after you can use the property of base class in child class
Related
In the following code, I want to assign an $settings's key, help to a constant class variable value, $To_default. However, I am told Constant expression contains invalid operations. Is there any way I can get around this?
class EmailClass extends PluginBase {
private $To_default = 'scrollout#stackoverflow.com';
protected $settings = array(
'To'=>array(
'type'=>'text',
'label'=>'To',
'help'=>$this->To_default, // Constant expression contains invalid operations
),
);
I've tried declaring $To_default in various ways including private const $To_default, static private $To_default, etc. but none worked. $settings is not static, as you can see, so I don't understand why this is a problem.
Don't know deep technical explanation for this but I think this is because normally you initialize properties in the constructor, like so:
class EmailClass
{
private $To_default = 'scrollout#stackoverflow.com'; // not a constant, so better throw it into constructor too
protected $settings;
public function __construct() {
$this->settings = array(
'To'=>array(
'type'=>'text',
'label'=>'To',
'help'=>$this->To_default
)
);
}
}
Analogical I don't know why: 'if isset(expression)' doesn't work but it doesn't have to. There's a better solution: 'if(expression)'. This is just how we do it.
You can not specify a default value that refers to another class property ($settings is an array that tries to read $To_default).
My recommendation is that $settings can be the result of a getter method, for example getSettings, and inside of it you can build an array and return it.
By doing do this, you can also decide to override the $To_default value with a setTo method.
Here is an example:
<?php
class EmailClass extends PluginBase {
private string $toDefault = '';
/**
* #param string $toDefault
*/
public function __construct( string $toDefault = 'scrollout#stackoverflow.com' ) {
$this->toDefault = $toDefault;
}
/**
* #param string $toDefault
*/
public function setToDefault( string $toDefault ): void
{
$this->toDefault = $toDefault;
}
public function getSettings(): array
{
return [
'TO' => [
'type' => 'text',
'label' => 'To',
'help' => $this->toDefault,
]
];
}
}
Use a class constant rather than a variable for the default.
Constants don't begin with $, that's the problem you had when you tried this.
class EmailClass extends PluginBase {
private const TO_DEFAULT = 'scrollout#stackoverflow.com';
protected $settings = array(
'To'=>array(
'type'=>'text',
'label'=>'To',
'help'=>self::TO_DEFAULT, // Constant expression contains invalid operations
),
);
}
I am creating a class that uses ReflectionProperty and am getting strange results.
Essentially, calling $reflectionProperty->getType()->getName() is returning the value of some entirely unrelated array. It does this under seemingly random conditions.
See below:
// this will be used as a type hinted property.
class DummyClass {
public function __construct($arr) {}
}
// a class that sets its property values reflectively
class BaseClass {
/** #var ReflectionProperty[] */
private static $publicProps = [];
/**
* Gets public ReflectionProperties of the concrete class, and caches them
* so we do not need to perform reflection again for this concrete class.
*
* #return ReflectionProperty[]
* #throws ReflectionException
*/
private function getPublicProps(){
if (!static::$publicProps) {
$concreteClass = get_class($this);
static::$publicProps = (new ReflectionClass($concreteClass))
->getProperties(ReflectionProperty::IS_PUBLIC);
}
return static::$publicProps;
}
/**
* For each public property in this class set value to the corresponding value from $propArr.
*
* #param $propArr
* #throws ReflectionException
*/
public function __construct($propArr) {
$concreteClass = get_class($this);
echo "Creating new instance of $concreteClass<br>";
foreach ($this->getPublicProps() as $prop) {
// get which property to set, its class, and value to pass to constructor
$propName = $prop->getName();
$propClass = $prop->getType()->getName();
$propValue = $propArr[$propName];
$propValueStr = var_export($propValue, true);
// print out what we are about to do, and assert $propClass is correct.
echo "---Setting: ->$propName = new $propClass($propValueStr)<br>";
assert($propClass === "DummyClass", "$propClass !== DummyClass");
// create the instance and assign it
$refClass = new ReflectionClass($propClass);
$this->$propName = $refClass->newInstanceArgs([$propValue]);
}
}
}
// a concrete implementation of the above class, with only 1 type hinted property.
class ConcreteClass extends BaseClass {
public DummyClass $prop1;
}
// should create an instance of ConcreteClass
// with ->prop1 = new DummyClass(["foo"=>"abc123"])
$testArr1 = [
"prop1" => ["foo" => "abc123"]
];
// should create an instance of ConcreteClass
// with ->prop1 = new DummyClass(["boo"=>"abc123def456"])
$testArr2 = [
"prop1" => ["boo" => "abc123def456"]
];
$tc1 = new ConcreteClass($testArr1);
echo "Created TestClass1...<br><br>";
$tc2 = new ConcreteClass($testArr2);
echo "Created TestClass2...<br><br>";
die;
The results:
Creating new instance of ConcreteClass
Setting: ->prop1 = new DummyClass(array ( 'foo' => 'abc123', ))
Created TestClass1...
Creating new instance of ConcreteClass
Setting: ->prop1 = new abc123def456(array ( 'boo' => 'abc123def456', ))
Error: assert(): abc123def456 !== DummyClass failed
Notice that the value of $propClass is abc123def456 -- how did that happen?
More Weirdness
Change the value of "abc123def456" to "12345678" and it will work.
Change the value of "abc123def456" to "123456789" and it will not work.
Omit the var_export(), and it will work. (Though, it may still break in other cases).
My gut tells me this is a PHP bug, but I might be doing something wrong, and/or this may be documented somewhere. I would like some clarification, because as of right now my only reliable solution is to not cache the reflected $publicProps. This results in an unnecessary call to ReflectionClass->getProperties() every single time I create a new ConcreteClass, which I'd like to avoid.
Turns out this was a bug in PHP: https://bugs.php.net/bug.php?id=79820
Minimal reproduction here: https://3v4l.org/kchfm
Fixed in 7.4.9: https://www.php.net/ChangeLog-7.php#7.4.9
I am trying to isolate the attributes of an originating class in an included trait. IE The trait should make an array of the names of all the attributes of the class but not the attributes of the trait for use within the trait.
I have tried doing this by extending a class. I have tried using static methods as per PHP: Is it possible to get the name of the class using the trait from within a trait static method? and I am getting nowhere.
I am about to use known attributes in the trait and simply remove them from the attribute array (as I know their names). This is a rather ugly solution but it will work.
Anyone see a better way to do this?
trait FooTrait
{
public $classVariables;
public function classAttributes()
{
$callingClass = get_class($this);
$rawAttributes= $this->$classVariables = get_class_vars($callingClass);
var_dump($rawAttributes);
var_dump($callingClass);
return $rawAttributes;
}
public function info()
{
var_dump($this->classVariables);
}
// manipulate $this -> classVaribales to do generic database operations
}
class Mine
{
use FooTrait;
protected $attrib1;
protected $attrib2;
protected $attrib3;
}
$needed = new Mine;
$needed->classAttributes();
$needed->info();
OUTPUT is attribute 1,2,3 and bar. How do I get just attribute 1, 2, 3?
EDIT: I edited a couple of attributes to try and make it more comprehensible.
UPDATE: This does not work if trait attributes are protected or private. As traits should not be directly referenced ... bit of a deal breaker.
The only way I could find to get the attributes of the trait WITHOUT those of the calling class was to name it as a literal. BUT that limits the scope and so cannot see the private and protected attributes.
I am giving up at this point and will use an array of the names of attributes used in the trait. Not a big problem just massively inelegant.
class ThisClass {
use ThisTrait;
public $classAttribute1 = 3;
public $classAttribute2 = 3;
public $classAttribute3 = 3;
}
trait ThisTrait {
public $traitTrait1 = 3;
public $traitTrait2 = 3;
public $traitTrait3 = 3;
public function classAttributes (){
$traitAttributes = get_class_vars("ThisTrait"); //NB String not variable
$traitAttributes = array_keys ($traitAttributes);
$className = get_class($this); //NB Var = gets class where this called
$classAttributes = get_class_vars($className);
$classAttributes = array_keys($classAttributes);
$classOnly = array_diff($classAttributes, $traitAttributes);
return $classOnly;
}
}
$thisClass = new ThisClass ();
$result = $thisClass -> classAttributes();
var_dump($result);
=========================================
array (size=3)
0 => string 'classAttribute1' (length=15)
1 => string 'classAttribute2' (length=15)
2 => string 'classAttribute3' (length=15)
I'm trying to instantiate some classes in an array. This is the scenario: I have many classes, e.g.:
class Class0 extends NumberedClasses{...}
class Class1 extends NumberedClasses{...}
class Class2 extends NumberedClasses{...}
They will increase over the time, so instead of instantiating them this way:
$instances0 = new Class0();
$instances1 = new Class1();
$instances2 = new Class2();
I want to have a getter method, like this one:
function getStrategies(){
return array(Class0, Class1, Class2);
}
So I can just add classses in that array in the future, and call it this way:
$strategies = $this->getStrategies();
for ($strategies as $strategy) {
$instance = new $strategy();
}
I'm used to do something similar with js, where I have the window object, so I can instantiate classes even just with a string (new window["Class1"]). But I'm new with PHP and can't find the way to do that.
Thanks in advance,
It may be suggested that using an Intermediary Class to manage these instances will be much ideal. Consider this:
<?php
class NumberedClasses{}
class Class0 extends NumberedClasses{}
class Class1 extends NumberedClasses{}
class Class2 extends NumberedClasses{}
class InstanceHelper {
/**
* #var array
*/
protected $instances = array();
public function addInstance($instanceKey, $instance) {
if(!array_key_exists($instanceKey, $this->instances)){
$this->instances[$instanceKey] = $instance ;
}
}
public function addInstances(array $arrayOfInstances) {
foreach($arrayOfInstances as $instanceKey=>$instance){
if(!array_key_exists($instanceKey, $this->instances)){
$this->instances[$instanceKey] = $instance ;
}
}
}
public function removeInstance($instanceKey) {
if(array_key_exists($instanceKey, $this->instances)){
unset($this->instances[$instanceKey]);
}
return $this->instances;
}
public function getAllInstances() {
return $this->instances;
}
}
Now; with the Class InstanceHelper, You can easily manage your instances... even add Instances, remove Instance and so on.... Again consider this tests:
<?php
$insHelper = new InstanceHelper();
$instances = array(
'c0' => new Class0(),
'c1' => new Class1(),
'c2' => new Class2(),
);
$insHelper->addInstances($instances);
var_dump($insHelper->getAllInstances());
var_dump($insHelper->removeInstance('c1'));
Here are the results of both var_dumps:
::VAR_DUMP 1::
array (size=3)
'c0' =>
object(Class0)[2]
'c1' =>
object(Class1)[3]
'c2' =>
object(Class2)[4]
::VAR_DUMP 2::
array (size=2)
'c0' =>
object(Class0)[2]
'c2' =>
object(Class2)[4]
I often use properties in my classes that store an array of options. I'd like to be able to somehow merge those options from defaults declared in a parent class.
I demonstrated with some code.
class A
{
public $options = array('display'=>false,'name'=>'John');
}
class B extends A
{
public $options = array('name'=>'Mathew');
}
Now when I create B, then I'd like $options to contain a merged array from A::options
What happens now is this.
$b = new B();
print_r($b);
array('name'=>'Mathew');
I would like something like this using array_merge_recursive().
array('display'=>false,'name'=>'Mathew');
Maybe it's something I could do in the constructor?
Is it possible to make this a behavior of class A? So that I don't always have to implement the same code in all subclasses.
Could I use reflection to auto find array properties in both classes and merge them?
In addition to the previous answers, another approach that may be suited for certain cases would be to use PHP Reflection or built-in class functions. Here is a basic example using the latter:
class Organism
{
public $settings;
public $defaults = [
'living' => true,
'neocortex' => false,
];
public function __construct($options = [])
{
$class = get_called_class();
while ($class = get_parent_class($class)) {
$this->defaults += get_class_vars($class)['defaults'];
}
$this->settings = $options + $this->defaults;
}
}
class Animal extends Organism
{
public $defaults = [
'motile' => true,
];
}
class Mammal extends Animal
{
public $defaults = [
'neocortex' => true,
];
}
$fish = new Animal();
print_r($fish->settings); // motile: true, living: true, neocortex: false
$human = new Mammal(['speech' => true]);
print_r($human->settings); // motile: true, living: true, neocortex: true, speech: true
I realize I changed your interface from a public variable to a method, but maybe it works for you. Beware, adding a naive setOps($ops) method may work unexpected if you allow the parent ops to continue to be merged in.
class A
{
private $ops = array('display'=>false, 'name'=>'John');
public function getops() { return $this->ops; }
}
class B extends A
{
private $ops = array('name'=>'Mathew');
public function getops() { return array_merge(parent::getOps(), $this->ops); }
}
class c extends B
{
private $ops = array('c'=>'c');
public function getops() { return array_merge(parent::getOps(), $this->ops); }
}
$c = new C();
print_r($c->getops());
out:
Array
(
[display] =>
[name] => Mathew
[c] => c
)
You can use a simple pattern like so:
abstract class Parent {
protected $_settings = array();
protected $_defaultSettings = array(
'foo' => 'bar'
);
public __construct($settings = array()) {
$this->_settings = $settings + $this->_defaultSettings;
}
}
In this way it's easily possible to modify the defaults applied in child classes:
class Child extends Parent {
protected $_defaultSettings = array(
'something' => 'different';
);
}
Or to apply something more complex:
class OtherChild extends Parent {
function __construct($settings = array()) {
$this->_defaultSettings = Configure::read('OtherChild');
return parent::__construct($settings);
}
}
Merging variables
Cake does come with a function for merging variables. It's used for controller properties such as components, helpers etc. But be careful applying this function to none trivial arrays - you may find that it doesn't quite do what you want/expect.