I'm trying make every day something new. Now i want to make function to dynamic load helper without include and creating new class object
In code igniter that's look like
$this->load->helper('syslog_helper');
and now we can use
syslog_helper->some_function()
file is automatic including, object is automatic created and we can use them
Question is: how can I do the same think using pure PHP ?
Like this
Create a class name load that has a method named helper, if you want it accessible with $syslog_helper then load needs to be able to call the original class so when you create an instance pass the $this as part of its constructor. Then the main class should use the magic __set method etc
Helper class to load:
class syslog_helper{
}
Loader class:
class loader{
protected $obj;
public function __construct($obj){
//save the controller instance as a property
$this->obj = $obj;
}
public function helper($class){
//require the class file - don't need to in this case as they are all in the same file for sandbox
//require_once HELPER_DIR.$class.'.php';
//create a new instance of Helper and assign it back to the controller
$this->obj->$class = new $class;
}
}
Base controller class
class foo{
public $data = [];
public $load;
public function __construct(){
//create the loader instance, pass an instance of the controller (this)
$this->load = new loader($this);
}
public function bar(){
//call the loader's helper() method with the name of the helper class
$this->load->helper('syslog_helper');
print_r($this->syslog_helper);
}
public function __set($key, $value){
//create a dynamic property
$this->data[$key] = $value;
}
public function __get($key){
//get a dynamic property
return $this->data[$key];
}
}
Call it:
(new foo)->bar();
Output:
syslog_helper Object
(
)
Sandbox
As you can see above, $this->syslog_helper gets populated with our helper class just like CI does it.
So it flows in this order:
$foo = new foo - create instance of controller, assign the loader class (with a back reference to the controller) $this->load = new loader($this);
$foo->bar() - call bar() this would be the request function in the controller, such as what the URL routes to.
$foo->load->helper('syslog_helper') - use the load property (instance of loader) to call its helper method, passing the name of the helper class as a string. Helper method should require the class file, and then create an instance of that class. new $class
$this->obj->$class = new $class; - then that instance is assigned to a dynamic property named the same as what was passed in
$this->obj->$class - Controller's __set magic method is triggered, storing instance of helper in Controler->data[$helper]
$foo->syslog_helper() - Controller's __get magic method is triggered returning Controler->data[$helper] or the instance of the helper we just created.
I just made this up, but I am sure CI is similar. You could look in the parent class of the Controllers etc.. and see how they do it.
Hope that makes sense...
One simple improvement you could make for the above code
I think CI does this, is to allow aliasing of the property... like this:
class loader{
protected $obj;
public function __construct($obj){
//save the controller instance as a property
$this->obj = $obj;
}
public function helper($class, $alias = null){
//if no alias default to the class name
if(!$alias) $alias = $class;
//require the class file - don't need to in this case as they are all in the same file for sandbox
//require_once APPPATH.'helpers/'.$class.'.php';
//create a new instance of Helper and assign it back to the controller
$this->obj->$alias = new $class;
}
}
Now if we did this in the controller's bar method:
class foo{
public function bar(){
//call the loader's helper() method with the name of the helper class
$this->load->helper('syslog_helper');
print_r($this->syslog_helper);
$this->load->helper('syslog_helper', 'syslog_helper_2');
print_r($this->syslog_helper_2);
}
Output:
syslog_helper Object
(
)
syslog_helper Object
(
)
You now have 2 instances of the helper, one named syslog_helper and the other syslog_helper_2. If we didn't alias them, the second call would simply overwrite the property in the controller leaving us with just one instance.
So you can see above we added a whole lot of flexibility with essentially 1 line of code. Big improvements don't have to be complex.
Sandboxy
Obviously you should flesh this out a bit more. By adding more things, like error checking for classes (files) that don't exist, __unset and __isset magic methods etc... But, this is the basic functionality you wanted.
Similarly you can add, model and library methods with the only real difference being the location. For that I would probably go with the magic __call method instead of 3 functions that do the same.
Implement load->model, load->library and load->helper
class loader{
protected $obj;
public function __construct($obj){
//save the controller instance as a property
$this->obj = $obj;
}
public function __call($method, $args){
//type => folder
$allowed = ['helper'=>'helpers','model'=>'models', 'library'=>'libraries'];
if(!isset($allowed[$method])) throw new Exception('Unknown method '.$method);
if(!isset($args[0])) throw new Exception('Missing required argument for method '.$method);
$class = $args[0];
$alias = isset($args[1]) ? $args[1] : $class;
//require the class file - don't need to in this case as they are all in the same file for sandbox
//require_once APPPATH.$allowed[$method].'/'.$class.'.php';
//create a new instance of Helper and assign it back to the controller
$this->obj->$alias = new $class;
}
}
Sandbox
Implement singletons
A singleton is basically re-using the same instance of the class for future calls, you can implement this with a few more changes to loader:
class syslog_helper{
public $test;
}
class loader{
protected $obj;
protected static $instances = [];
public function __construct($obj){
$this->obj = $obj;
}
public function __call($method, $args){
//type => folder
$allowed = ['helper'=>'helpers','model'=>'models', 'library'=>'libraries'];
if(!isset($allowed[$method])) throw new Exception('Unknown method '.$method);
if(!isset($args[0])) throw new Exception('Missing required argument for method '.$method);
$class = $args[0];
$alias = isset($args[1]) ? $args[1] : $class;
//if this is the first time we instantiated [$method][$alias] save it
if(!isset(static::$instances[$method][$alias])){
//require the class file - don't need to in this case as they are all in the same file for sandbox
//require_once APPPATH.$allowed[$method].'/'.$class.'.php';
//create a new instance of Helper and assign it back to the controller
static::$instances[$method][$alias] = new $class;
}
//return the saved static instance
$this->obj->$alias = static::$instances[$method][$alias];
}
}
class foo{
public $data = [];
public $load;
public function __construct(){
$this->load = new loader($this);
}
public function bar(){
$this->load->helper('syslog_helper');
print_r('bar::Test before: '.$this->syslog_helper->test."\n");
$this->syslog_helper->test = 'test';
print_r('bar:Test after: '.$this->syslog_helper->test."\n");
}
public function biz(){
$this->load->helper('syslog_helper');
print_r('biz:Test: '.$this->syslog_helper->test."\n");
}
public function __set($key, $value){
$this->data[$key] = $value;
}
public function __get($key){
return $this->data[$key];
}
}
$foo = new foo;
$foo->bar();
$foo->biz();
Output:
bar::Test before:
bar:Test after: test
biz:Test: test
Sandbox
The important thing here is that, when we call $this->load->helper('syslog_helper'); from biz() in the controller we are getting the same instance of the helper we created previously. You can tell this because the public property I added to the helper retains it's value we set in bar(). You could actually call this anywhere in your code and get the same instance with the same data stored in it, it was just easier (shorter) for examples sake to do it this way.
This is useful if you need the same helper in multiple classes, instead of creating multiple instances you can re-use them. I am not sure if CI does this right off the top of my head... lol
In this case I think it's acceptable to do them as a singleton, if you need a new copy you can just alias it and then that would be a independent instance.
One last thing I should add, is that CI probably doesn't pass the controller instance to the loader class. This is because CI calls the controller from the routing so it already has the instance of the Controller at it's disposal. And as CI is a singleton it can probably be accessed with $CI = get_instance(); inside of loader, so there is no need to pass it along in the the way I showed inside of the CI framework. Basically they just access the same data in a different way.
Cheers!
Related
I want to declare a non-public constructor so it's users of the class can't call new Message() directly but have to instantiate the object from a static builder method declared on an abstract class that Message extends.
So far my code is:
abstract class SqlDecodable {
public function instanceFromRawSql (array $rawSql) {
$newInstanceToReturn = new static() // problem is here
// .. map the new instance ..
return $newInstance ;
}
}
// for exemple...
class Message extends SqlDecodable {
private $receiverId ;
private $senderId ;
private $text ;
private/protected/public?? function __construct() {
// problem here : this constructor should be usable only by
parent class, not for user of Message
}
static function propertiesToSqlFields() {
return [
"receiverId" => "receiver_id_field_in_db",
"senderId" => "sender_id",
"text" => "text"
]
}
}
This is actually more complicated, but I simplified the system for this question
When I implement my method instanceFromRawSqlArray, I have to create a new instance of the child class: $instanceToReturn = new static(), and set the variables one by one afterwards.
Though, I don't want to let a __construct that takes no args in my model classes. I don't want the dev user of Message to be able to new Message().
This constructor should be usable only by instanceFromRawSqlArray.
The problem is that, as I saw, there is no C++ friends class in PHP. I can't make my __construct protected, because, as I saw, protected methods are accessibles for childs, not for parent.
Do you have ideas to map this new instance in the method instanceFromRawSqlArray, without creating any constructor or setter that would corrupt my model class "encapsulation protections"?
You were very close. You can simply declare your constructor to be protected.
Instantiating the class directly won't work, but you can call new from the static method declared in the abstract class.
E.g.:
abstract class SuperAbstract {
static function create() {
return new static();
}
}
class Extended extends SuperAbstract {
private $hello = '';
protected function __construct() {
$this->hello = "world";
}
public function hello() {
return "hello " . $this->hello;
}
}
// works
$a = Extended::create();
echo $a->hello(); // outputs "hello world"
// can't touch this. This will fail because the constructor is `protected`.
$b = new Extended();
Of course, since it's protected the constructor could also be called from children classes. That's unavoidable, as long as children classes are a possibility. But you could also declare Extended as final, making extension of the class impossible. Thus, it would only be possible to create new instances from the factory method defined in the abstract parent.
final Extended extends SuperAbstract
protected function __construct() { }
}
You can see it working (and failing), here: https://3v4l.org/LliKj
I have a class that using an external package to do something
class MyClass
{
public function doSomething($data){
$external = new External();
$external->doSomething($data);
}
}
This class is called from another class, for example:
class MasterClass
{
public function go(){
$data = 'whatever';
$data2 = 'whatever2';
$myClass = new MyClass();
$myClass->doSomething($data);
$myClass->doSomething($data2);
....
}
}
So in my MasterClass I am calling the doSomething function multiple times. WHich creates a new External class multiple times - which is not really necessary.
How can I get around this issue and only create the external class once?
class MyClass
{
protected $external;
public function doSomething($data){
if(!$this->external){
$this->external = new External();
}
$this->external->doSomething($data);
}
}
But read about dependency injection in php.
Move the new External() call into the constructor and store it as a property, then reference that property in the doSomething() method instead of constructing a new instance every time.
Alternatively, if you don't want to always construct a new External whenever you construct a MyClass, you could move the construction into a Lazy Load static method called something like getExternal() in External class.
The first time that method is called it would need to store a new instance of External as a property, but on subsequent calls (when the property is already set) return the same instance. This type of pattern is called Singleton Pattern in Object-Oriented Design Patterns.
You could refer to this link to know more about singleton pattern and how it is implemented.
Pass External class in constructor.
class MyClass
{
private $external;
public function __construct(External $external)
{
$this->external = $external;
}
public function doSomething($data)
{
$this->external->doSomething($data);
}
class MasterClass
{
public function go() {
$data = 'whatever';
$data2 = 'whatever2';
$external = new External();
$myClass = new MyClass($external);
$myClass->doSomething($data);
$myClass->doSomething($data2);
....
}
}
$model = new static($variable);
All these are within a method inside a class, I am trying to technically understand what this piece of code does. I ran around in the Google world. But can't find anything that leads me to an answer. Is this just another way of saying.
$model = new static $variable;
Also what about this
$model = new static;
Does this mean I'm initializing a variable and settings it's value to null but I am just persisting the variable not to lose the value after running the method?
static in this case means the current object scope. It is used in late static binding.
Normally this is going to be the same as using self. The place it differs is when you have a object heirarchy where the reference to the scope is defined on a parent but is being called on the child. self in that case would reference the parents scope whereas static would reference the child's
class A{
function selfFactory(){
return new self();
}
function staticFactory(){
return new static();
}
}
class B extends A{
}
$b = new B();
$a1 = $b->selfFactory(); // a1 is an instance of A
$a2 = $b->staticFactory(); // a2 is an instance of B
It's easiest to think about self as being the defining scope and static being the current object scope.
self is simply a "shortcut name" for the class it occurs in. static is its newer late static binding cousin, which always refers to the current class. I.e. when extending a class, static can also refer to the child class if called from the child context.
new static just means make new instance of the current class and is simply the more dynamic cousin of new self.
And yeah, static == more dynamic is weird.
You have to put it in the context of a class where static is a reference to the class it is called in. We can optionally pass $variable as a parameter to the __construct function of the instance you are creating.
Like so:
class myClass {
private $variable1;
public function __construct($variable2) {
$this->variable1 = $variable2;
}
public static function instantiator() {
$variable3 = 'some parameter';
$model = new static($variable3); // <-this where it happens.
return $model;
}
}
Here static refers to myClass and we pass the variable 'some parameter' as a parameter to the __construct function.
You can use new static() to instantiate a group of class objects from within the class, and have it work with extensions to the class as well.
class myClass {
$some_value = 'foo';
public function __construct($id) {
if($this->validId($id)) {
$this->load($id);
}
}
protected function validId($id) {
// check if id is valid.
return true; // or false, depending
}
protected function load($id) {
// do a db query and populate the object's properties
}
public static function getBy($property, $value) {
// 1st check to see if $property is a valid property
// if yes, then query the db for all objects that match
$matching_objects = array();
foreach($matching as $id) {
$matching_objects[] = new static($id); // calls the constructor from the class it is called from, which is useful for inheritance.
}
return $matching_objects;
}
}
myChildClass extends myClass {
$some_value = 'bar'; //
}
$child_collection = myChildClass::getBy('color','red'); // gets all red ones
$child_object = $child_collection[0];
print_r($child_object); // you'll see it's an object of myChildClass
The keyword new is used to make an object of already defined class
$model = new static($variable);
so here there is an object of model created which is an instance of class static
I'm struggling to get my Behavior class to use an object instance in the callbacks.
class SomethingBehavior extends ModelBehavior
{
public function setObject($obj)
{
// do stuff
}
public function afterFind(Model $model,$results,$primary)
{
// use the $obj reference set above
}
}
Now I need the Model class to call setObject(..) before any find operations are performed. So ideally I would just assign the object I need in the constructor.
class Document extends AppModel
{
//.....
public function __construct($id,$table,$ids)
{
parent::__construct($id,$table,$ds);
$this->Something->setObject(new MyClass());
}
}
My problem is that the Behavior object isn't yet configured, and I get a not an object error when trying to use it.
I can't find any callback method for Models like in Components. For example, there is no setup or initialize method.
How can I assign the object I need to the Behavior?
You don't seem to have worked with behaviors much. Try to use the containable, tree or other core or plugin behaviors, then you will soon figure out the basics.
First of all, behaviors are attached to models (and since 2.3: loaded), not the other way around. A model then gets "richer" in functionality.
Either statically be using public $actsAs or dynamically using
$this->Behaviors->attach('Something'); // since 2.3: load() instead of attach()
It can directly access the behavior methods. Lets say we have a method foo() in your behavior.
You can then call it from your model as
$this->foo($foo, $bar);
Or from your controller as
$this->Document->Behaviors->attach('Something')
$this->Document->foo($foo, $bar);
Awesome, right?
The behavior method usually has this declaration:
public function foo(Model $Model, $foo, $bar) {
$alias = $Model->alias;
// do sth
}
As you can see, you always pass the model into it implicitly (as first argument automatically passed).
You can access all its attributes.
And do not touch the constructor of the model. no need to do that.
If you really need to pass an object in at runtime, why does your approach not work?
public function setObject(MyClass $obj) {
$this->Obj = $obj;
}
Now you can internally use the object from your behavior methods
public function doSth(Model $Model) {
$this->Obj->xyz();
}
Also this might not be the most elegant approach.
You never set the something member of the Document class. You either need to instantiate it inside the constructor, or pass it in.
Personally, I would do something like this:
class Document extends AppModel
{
private $behavior;
public function __construct($id,$table,$ids, ModelBehavior $behavior)
{
parent::__construct($id,$table,$ds);
$this->behavior = $behavior
$this->behavior->setObject(new MyClass());
}
}
$doc = new Document(..., new SomethingBehavior());
Or better yet, you could even separate it further by doing:
class Document extends AppModel
{
private $behavior;
public function __construct($id,$table,$ids, ModelBehavior $behavior)
{
parent::__construct($id,$table,$ds);
$this->behavior = $behavior
}
}
$behavior = new SomethingBehavior();
$behavior->setObject(new MyClass());
$doc = new Document(..., $behavior);
That way, there is less magic going on in the constructor.
I am having trouble calling a specific method from another class in my app. I have a class, Rest, that determines various settings, etc. about a particular request received by the server and creates a Rest object with the properties of the request. The Rest class may then call any given method in a separate class to fulfill the request. The problem is that the other class needs to call methods in the Rest class to send a response, etc.
How can this be possible? Here's a blueprint of my current setup:
class Rest {
public $controller = null;
public $method = null;
public $accept = null;
public function __construct() {
// Determine the type of request, etc. and set properties
$this->controller = "Users";
$this->method = "index";
$this->accept = "json";
// Load the requested controller
$obj = new $this->controller;
call_user_func(array($obj, $this->method));
}
public function send_response($response) {
if ( $this->accept == "json" ) {
echo json_encode($response);
}
}
}
The controller class:
class Users {
public static function index() {
// Do stuff
Rest::send_response($response_data);
}
}
This results in receiving a fatal error in the send_response method: Using $this when not in object context
What's the better way to do this without sacrificing the current workflow.
You can create a Rest instance in User:
public static function index() {
// Do stuff
$rest = new Rest;
$rest::send_response($response_data);
}
You could also change Rest to be a singleton and call an instance of it, but beware of this antipattern.
You need to create an instance first.
class Users {
public static function index() {
// Do stuff
$rest = new Rest();
$rest->send_response($response_data);
}
}
You don't call send_response() in an object context, as the error message says.
Either you create an instance and call everything on that instance (IMHO the right way), or you do everything statically, including the constructor (you may want to have a intialization method instead) and the properties.