I am creating a little system which will allow users to extend the system with their own classes.
Class Core {
static $confArray;
static $extendArray;
protected static $instance;
public function read($name)
{
return self::$confArray[$name];
}
public function put($name, $value)
{
self::$confArray[$name] = $value;
}
public function extend($function, $handler, $args=null){
self::$extendArray[$function] = new $handler($args);
}
public function __call($method, $args){
return self::$extendArray[$method];
}
public static function getInstance()
{
if (!isset(self::$instance))
{
$object =__CLASS__;
self::$instance= new $object;
}
return self::$instance;
}
}
With That, now a user can come and register a simple extension from such a class:
class WorkersTest{
function isWorking($who){
echo "$who is working";
}
function isNotWorking($who){
echo "$who is not working";
}
}
To call the function (isworking/ isNotWorking), a the programmer needs to register the test class through:
Core::getInstance->extend("worker","WorkersTest");
Then it can now be called through:
Core::getInstance->worker()->isWorking("George");
This is working perfectly. My question is how i can remove the () in the call (dont worry why) and have:
Core::getInstance->worker->isWorking("George");
Is it possible?
You can use the magic __get() method, just like __call():
public function __get($name)
{
return $this->$name();
}
Try overriding the magic __get() method to return what you need:
Class Core {
// (...)
public function __get($name) {
if (isset( self::$extendArray[$function] )) {
return $this->$name();
}
//if there is no function registered under named "$name"
//throwing Exception is by design better, as #scragar suggested
throw new Exception("No function registered under named {$name}");
//return NULL;
}
}
Related
It doesn't save my changes after adding a category.
If I add a category, it is seen in the overview, but if I refresh I see the original amount.
I guess there is an error in my Singleton-design but I can't seem to find it.
class ProductService {
private $_database;
public function __construct($databaseType) {
$databaseFactory = new DatabaseFactory();
$this->_database = $databaseFactory->createDatabase($databaseType);
}
public function addCategory($category){
$this->_database->addCategory($category);
}
public function getAllCategories() {
return $this->_database->getAllCategories();
}
}
class DatabaseFactory {
public function __construct() {
}
public function createDatabase($type){
switch ($type) {
case "Memory" :
return MemoryDatabase::getInstance();
}
}
}
class MemoryDatabase {
private $categories;
private function __construct() {
$this->categories = array(
new Category("Cheese"),
);
}
public static function getInstance() {
static $inst = null;
if ($inst === null) {
$inst = new MemoryDatabase();
}
return $inst;
}
private function __clone() {}
private function __wakeup() {}
public function addCategory($category) {
array_push($this->categories, $category);
}
public function getAllCategories() {
return $this->categories;
}
}
Each request you perform in PHP is stateless.
If you want to persist data between requests, you will need to put your data in some form of persistant storage, i.e., sessions, filesystem, database, memory, etc.
Singleton pattern only ensures a single copy of an object is created, for a given request.
I have a class that extends from Yii2's Model and I need to declare a class public property in the constructor, but I'm hitting a problem.
When I call
class Test extends \yii\base\Model {
public function __constructor() {
$test = "test_prop";
$this->{$test} = null; // create $this->test_prop;
}
}
Yii tries to call, from what I understand, the getter method of this property, which of course doesn't exist, so I hit this exception.
Also, when I actually do $this->{$test} = null;, this method gets called.
My question is: Is there a way to declare a class public property in another way? Maybe some Reflexion trick?
You could override getter/setter, e.g. :
class Test extends \yii\base\Model
{
private $_attributes = ['test_prop' => null];
public function __get($name)
{
if (array_key_exists($name, $this->_attributes))
return $this->_attributes[$name];
return parent::__get($name);
}
public function __set($name, $value)
{
if (array_key_exists($name, $this->_attributes))
$this->_attributes[$name] = $value;
else parent::__set($name, $value);
}
}
You could also create a behavior...
Ok, I received help from one of Yii's devs. Here is the answer:
class Test extends Model {
private $dynamicFields;
public function __construct() {
$this->dynamicFields = generate_array_of_dynamic_values();
}
public function __set($name, $value) {
if (in_array($name, $this->dynamicFields)) {
$this->dynamicFields[$name] = $value;
} else {
parent::__set($name, $value);
}
}
public function __get($name) {
if (in_array($name, $this->dynamicFields)) {
return $this->dynamicFields[$name];
} else {
return parent::__get($name);
}
}
}
Note that I'm using in_array instead of array_key_exists because the dynamicFields array is a plain array, not an associative one.
EDIT: This is actually wrong. See my accepted answer.
Try to set variable in init method.
Like this:
public function init() {
$test = "test_prop";
$this->{$test} = null; // create $this->test_prop;
parent::init();
}
So this is my mixin class:
class AisisCore_Loader_Mixins {
private $_classes;
private $_class_objects = array();
private $_methods = array();
public function __construct(){
$this->init();
}
public function init(){}
public function setup($class){
if(!is_array($class)){
throw new AisisCore_Loader_LoaderException('Object passed in must be of type $class_name=>$params.');
}
$this->_classes = $class;
$this->get_class_objects();
$this->get_methods();
}
public function get_class_objects(){
foreach($this->_classes as $class_name=>$params){
$object = new ReflectionClass($class_name);
$object_name = get_class($object);
$this->_class_objects[$object->name] = $object->newInstanceArgs($params);
}
}
public function get_methods(){
foreach($this->_class_objects as $class_object_name => $class_object){
$this->_methods[$class_object_name] = get_class_methods($class_object);
}
return $this->_methods;
}
public function __call($name, $param = null){
foreach($this->_methods as $class_name=>$methods){
foreach($methods as $method){
if($name === $method){
return $this->isParam($class_name, $method, $param);
}
}
}
throw new AisisCore_Loader_LoaderException("Method: " .$name.
" does not exist or it's access is not public");
}
private function isParam($class_name, $method, $param){
if($param != null){
call_user_func(array($class_name, $method), $param);
}else{
call_user_func(array($class_name, $method));
}
}
}
Pretty simple stuff, load a set of classes, allow you to call their functions and so on, but we have a new issue. It seems that classes passed into this are instantiated as static, thus their methods cannot use $this-> they are resorted to using self:: which is wrong.
Lets see an example of how this all works:
class BaseBridge extends AisisCore_Loader_Mixins{
public function __construct(){
parent::construct();
$this->setup(array('ClassB' => array()));
}
}
Lets Define ClassB
class ClassB{
public function __construct(){}
public function some_method(){
$this->_some_private_method();
}
private function _some_private_method(){}
}
Pretty basic stuff, so lets hook it all up in ClassA
class ClassA extends BaseBridge{
public function __construct(){
parent::__construct();
$this->some_method();
}
}
Quick Review: We have a core class, ClassA which extends BaseBridge which is our bridge class between one or more (meant to be used with more) classes that ClassA extends from. In this case were only extending from ClassB for simplicity.
Whats the issue? See, how in ClassB, were doing: $this->_some_private_method(); ya that's going to epically and catastrophically fail. Why? because I get the error: Using $this when not in object context which makes me so confused, so I change it to: self::$_some_private_method(); and it works like a charm.
Why? and what do I have to change or fix to make it so that $this can be used in a class being instantiated through the mixin class?
So with some slight modifications, I have managed to make this work. How ever I do not believe that a function with multiple arguments will work - Feed back appreciated.
class AisisCore_Loader_Mixins {
private $_classes;
private $_class_objects = array();
private $_methods = array();
public function __construct(){
$this->init();
}
public function init(){}
public function setup($class){
if(!is_array($class)){
throw new AisisCore_Loader_LoaderException('Object passed in must be of type $class_name=>$params.');
}
$this->_classes = $class;
$this->get_class_objects();
$this->get_methods();
}
public function get_class_objects(){
foreach($this->_classes as $class_name=>$params){
$object = new ReflectionClass($class_name);
$this->_class_objects[$object->name] = $object->newInstanceArgs($params);
}
}
public function get_methods(){
foreach($this->_class_objects as $class_object_name => $class_object){
$this->_methods[$class_object_name] = get_class_methods($class_object);
}
return $this->_methods;
}
public function __call($name, $param = null){
foreach($this->_methods as $class_name=>$methods){
foreach($methods as $method){
if($name === $method){
return $this->_is_param($class_name, $method, $param);
}
}
}
throw new AisisCore_Loader_LoaderException("Method: " .$name.
" does not exist or it's access is not public");
}
private function _is_param($class_name, $method, $param){
if($param != null){
$this->_param_is_array($class_name, $method, $param);
}else{
call_user_func(array($this->_class_objects[$class_name], $method));
}
}
private function _param_is_array($class_name, $method, $param){
if(is_array($param)){
call_user_func_array(array($this->_class_objects[$class_name], $method), $param);
}else{
call_user_func(array($this->_class_objects[$class_name], $method, $param));
}
}
}
Now functions inside of classes that are registered by this class can use $this->.
The issue is that I am not sure if multiple param based functions will actually work.
Consider the following class
class myClass {
private $model;
public function update($input) {
return $this->model->update($input);
}
public function find($id) {
$this->model = ORMfind($id);
}
}
How do I prevent
$myClass = new myClass;
$myClass->update($input);
The problem isn't HOW to use the above code but how to make update() a method only callable after find().
EDIT: I changed what my method does so it was more clearly understood that I need to do one method (find()) before another (update())
You could add a flag to your code like so:
class myClass {
private $model;
private $canUpdate = 0;
public function update($input) {
if ($canUpdate === 0) return; // or throw an exception here
return $this->model->update($input);
}
public function find($id) {
$this->model = ORMfind($id);
$canUpdate = 1;
}
}
Setting the flag $canUpdate will caution the update() method to react accordingly. If update() is called, you can throw an exception or exit out of the method if the flag is still 0.
To prevent from returning null value by get :
public function get() {
if (isset($this->value)) return $this->value;
else echo "please give me a value ";
}
You can also create a construct:
function __construct($val){
$this->value=$val;
}
and then give a value to your $value without using set() method:
$myClass=new myClass(10);
Outputting text, returning void, I think all of this is wrong. When you do not expect something to happen, you should throw an exception:
class MyClass {
private $canUpdate = false;
public function find($id) {
// some code...
$this->canUpdate = true;
}
public function canUpdate() {
return $this->canUpdate;
}
private function testCanUpdate() {
if (!$this->canUpdate()) {
throw new Exception('You cannot update');
}
}
public function update($inpjut) {
$this->testCanUpdate();
// ... some code
}
}
Now you can do:
$obj = new MyClass();
try {
$obj->update($input);
} catch (Exception $e) {
$obj->find($id);
$obj->update($input);
}
The proper way to make sure ->update() can only be called when the model has been initialized is to turn it into a dependency:
class myClass
{
private $model;
public function __construct($id)
{
$this->model = ORMfind($id);
}
public function update($input) {
return $this->model->update($input);
}
}
$x = new myClass('123');
Alternatively, if you have multiple find operations, you could introduce them as static constructor methods:
class myClass
{
private $model;
private function __construct($model)
{
$this->model = $model;
}
public function update($input) {
return $this->model->update($input);
}
public static function find($id)
{
return new self(ORMfind($id));
}
}
$x = myClass::find('123');
Update
Tackling your immediate problem can be done by a simple check:
public function update($input) {
return $this->model ? $this->model->update($input) : null;
}
We have a class that holds a public array called $saved that contains lots of data required to share between methods (example below)...
class Common {
public $saved = array();
public function setUser($data) {
$this->saved['user_data'] = $data;
}
public function getUserID() {
return $this->saved['user_data']['id'];
}
}
There are literally thousands of lines of code that work like this.
The problem is that new instance of classes that extend Common are being made within some methods so when they access $saved it does not hold the same data.
The solution is to make $saved a static variable, however I can't change all of the references to $this->saved so I want to try and keep the code identical but make it act static.
Here is my attempt to make $this->saved calls static...
class PropertyTest {
private $data = array();
public function __set($name, $value) {
$this->data[$name] = $value;
}
public function __get($name) {
if (array_key_exists($name, $this->data)) {
return $this->data[$name];
}
return null;
}
public function __isset($name) {
return isset($this->data[$name]);
}
public function __unset($name) {
unset($this->data[$name]);
}
}
class Common {
public $saved;
private static $_instance;
public function __construct() {
$this->saved = self::getInstance();
}
public static function getInstance() {
if (self::$_instance === null) {
self::$_instance = new PropertyTest();
self::$_instance->foo = array();
}
return self::$_instance->foo;
}
}
This doesn't quite work when setting a variable it doesn't seem to stay static (test case below)...
class Template extends Common {
public function __construct() {
parent::__construct();
$this->saved['user_data'] = array('name' => 'bob');
$user = new User();
}
}
class User extends Common {
public function __construct() {
parent::__construct();
$this->saved['user_data']['name'] .= " rocks!";
$this->saved['user_data']['id'] = array(400, 10, 20);
}
}
$tpl = new Template();
print_r($tpl->saved['user_data']);
$this->saved is empty when User gets initialized and doesn't seem to be the same variable, the final print_r only shows an array of name => bob.
Any ideas?
First of all, I have to say that, IMO, it is not that good to use an instance's property as a class's property ($saved is not declared as static but its value is shared with all instance).
Here is a working version http://codepad.org/8hj1MOCT, and here is the commented code. Basically, the trick is located in using both ArrayAccess interface and the singleton pattern.
class Accumulator implements ArrayAccess {
private $container = array();
private static $instance = null;
private function __construct() {
}
public function getInstance() {
if( self::$instance === null ) {
self::$instance = new self();
}
return self::$instance;
}
public function offsetSet($offset, $value) {
if (is_null($offset)) {
$this->container[] = $value;
} else {
$this->container[$offset] = $value;
}
}
public function offsetExists($offset) {
return isset($this->container[$offset]);
}
public function offsetUnset($offset) {
unset($this->container[$offset]);
}
public function offsetGet($offset) {
return isset($this->container[$offset]) ? $this->container[$offset] : null;
}
}
class Common {
public $saved = null;
public function __construct() {
// initialize the "saved" object's property with the singleton
// that variable can be used with the array syntax thanks to the ArrayAccess interface
// so you won't have to modify your actual code
// but also, since it's an object, this local "$this->saved" is a reference to the singleton object
// so any change made to "$this->saved" is in reality made into the Accumulator::$instance variable
$this->saved = Accumulator::getInstance();
}
public function setUser($data) {
$this->saved['user_data'] = $data;
}
public function getUser() {
return $this->saved['user_data'];
}
}
class Template extends Common {
// you can redeclare the variable or not. Since the property is inherited, IMO you should not redeclare it, but it works in both cases
// public $saved = null;
public function __construct() {
// maybe we can move this initialization in a method in the parent class and call that method here
$this->saved = Accumulator::getInstance();
}
}
I think there are a number of issues with this implementation that could well come back to bite you. However, in your current implementation your contructing a new instance (albeit through a static call) every time.
Instead use getInstance() as your singleton hook, and make your __construct private, as you'll only be accessing it from with the context of the Common class.
Like so:
class Common {
public $saved;
private static $_instance;
private function __construct() {
}
public static function getInstance() {
if (self::$_instance === null) {
self::$_instance = new self();
... any other modifications you want to make ....
}
return self::$_instance;
}
}
And don't ever run parent::_construct(), instead always use the getInstance() method.
You might also want to ditch the idea of extending this singleton class. This is really a bad antipattern and could cost you a number of issues in the long run. Instead just maintain a Common class that other classes can read / write to. As its a singleton you don't need to worry about injection.
I seem to have solved the problem, by making $this->saved a reference to a static variable it works...
class Common {
private static $savedData = array();
public $saved;
public function __construct() {
$this->saved =& self::$savedData;
}
}