I am curious about the best practices and any performance or other considerations relating to passing an instance of an object as a parameter to another function in the same class vs creating another instance of that object in the new function. Here's a quick example:
Option 1: Pass both instance of Trainee AND TraineeController to other functions
protected function startTraining($traineeID) {
$traineeController = new TraineeController();
$trainee = $traineeController->findTrainee($traineeID);
$this->initializeTraining($trainee, $traineeController);
$this->doSomeOtherStuffWithTrainee($trainee, $traineeController);
return Redirect::back()->with('trainee', $trainee);
}
protected function initializeTraining($trainee, $traineeController) {
$trainee->blah1 = 'red';
$trainee->blah2 = 'blue';
$propertiesToUpdate = [
'blah1' => $trainee->blah1,
'blah2' => $trainee->blah2
];
$traineeController->updateTrainee($trainee->traineeID, $propertiesToUpdate);
}
Option 2: Pass $trainee ONLY, instantiate a new TaineeController each time
protected function startTraining($traineeID) {
$traineeController = new TraineeController();
$trainee = $traineeController->findTrainee($traineeID);
$this->initializeTraining($trainee);
$this->doSomeOtherStuffWithTrainee($trainee);
return Redirect::back()->with('trainee', $trainee);
}
protected function initializeTraining($trainee) {
$trainee->blah1 = 'red';
$trainee->blah2 = 'blue';
$propertiesToUpdate = [
'blah1' => $trainee->blah1,
'blah2' => $trainee->blah2
];
$traineeController = new TraineeController();
$traineeController->updateTrainee($trainee->traineeID, $propertiesToUpdate);
}
In the above I need to pass $trainee across all functions each time instead of creating a new trainee from $traineeID because some other stuff goes on behind the scenes during the 'training' process that would otherwise be lost before relevant data is saved to the db. However, this is not required for TraineeController - I can either pass it as a parameter or instantiate a new TraineeController as much as I want. Which is the better choice?
I saw this question relating to C#, where the accepted answer was that passing an entire object is usually more efficient and instantiating another one because you are passing by reference. Does this hold true for PHP? Ie is the most efficient approach to pass the entire object by reference to required functions using &?
There is nothing wrong with passing an object as reference, but note that php expects that your function argument needs to expect a reference rather than just passing a variable by reference (php docs). php 5.4.0 will even raise a fatal error if this is not respected:
right:
protected function initializeTraining($trainee, &$traineeController) {}
$this->initializeTraining($trainee, $traineeController);
wrong:
protected function initializeTraining($trainee, $traineeController) {}
$this->initializeTraining($trainee, &$traineeController);
Passing objects by reference will in most cases have better performance than initiating the object again, but passing by reference could become tricky if your object has its own properties:
class TraineeController {
$fooCalled = false;
function foo(){ $this->fooCalled = true; }
function isFooCalled(){ return $this->fooCalled; }
}
$traineeController = new TraineeController();
$traineeController->foo();
//&$traineeController->isFooCalled() will be different from
//new TraineeController()->isFooCalled().
Related
I have a method, which takes a reference
// CarService.php
public function getCars(&$carCollection = null)
{
$promise = // guzzle request for getting all cars would be here
$promise->then(function (ResponseInterface $response) use (&$carCollection) {
$cars= json_decode($response->getBody(), true);
$carCollection= new CarCollection($cars);
});
}
However, when accessing the collection and trying to reuse it, I'm getting the error
Argument 1 passed to {placeholder} must be an instance of {placeholder}, null given
I know that the reason for this is, that the constructor returns nothing, but how can I still assign my variable to a new instance of the CarCollection (which extends Doctrine's ArrayCollection)
I even tried it with a static method as a work around
// CarCollection.php
public static function create(array $cars): CarCollection
{
$carCollection = new CarCollection($cars);
return $carCollection;
}
// CarService.php
public function getCars(&$carCollection = null)
{
$cars = // curl request for getting all cars would be here
$carCollection = CarCollection::create($cars)
}
but it's still null. Why is that? How can I set a referenced variable to a new class?
I access the method like this
$carService = $this->get('tzfrs.vehicle.services.car');
$carCollection = null;
$promises = [
$carService->getCars($carCollection)
];
\GuzzleHttp\Promise\unwrap($promises);
var_dump($carCollection); // null
When I set the reference directly, eg.
// CarService.php
public function getCars(&$carCollection = null)
{
$carCollection = new CarCollection([]);
}
it works without any problems. Seems like the callback is somehow the problem.
Whoever downvoted this, can you please elaborate why and why you voted to close?
I might be misunderstanding the question, but you should be able to modify an object when passing by reference. See here for an example: https://3v4l.org/KtFvZ
In the later example code that you added, you shouldn't pass $carCollection by reference, the & should only be in the method/function defintion, not provided when you call it. I don't think that is your problem though, that should be throwing an error in php7.
How can I check if an object will be successfully instantiated with the given argument, without actually creating the instance?
Actually I'm only checking (didn't tested this code, but should work fine...) the number of required parameters, ignoring types:
// Filter definition and arguments as per configuration
$filter = $container->getDefinition($serviceId);
$args = $activeFilters[$filterName];
// Check number of required arguments vs arguments in config
$constructor = $reflector->getConstructor();
$numRequired = $constructor->getNumberOfRequiredParameters();
$numSpecified = is_array($args) ? count($args) : 1;
if($numRequired < $numSpecified) {
throw new InvalidFilterDefinitionException(
$serviceId,
$numRequired,
$numSpecified
);
}
EDIT: $constructor can be null...
The short answer is that you simply cannot determine if a set of arguments will allow error-free instantiation of a constructor. As commenters have mentioned above, there's no way to know for sure if a class can be instantiated with a given argument list because there are runtime considerations that cannot be known without actually attempting
instantiation.
However, there is value in trying to instantiate a class from a list of constructor arguments. The most obvious use-case for this sort of operation is a configurable Dependency Injection Container (DIC). Unfortunately, this is a much more complicated operation than the OP suggests.
We need to determine for each argument in a supplied definition array whether or not it matches specified type-hints from the constructor method signature (if the method signature actually has type-hints). Also, we need to resolve how to treat default argument values. Additionally, for our code to be of any real use we need to allow the specification of "definitions" ahead of time for instantiating a class. A sophisticated treatment of the problem will also involve a pool of reflection objects (caching) to minimize the performance impact of repeatedly reflecting things.
Another hurdle is the fact that there's no way to access the type-hint of a reflected method parameter without calling its ReflectionParameter::getClass method and subsequently instantiating a reflection class from the returned class name (if null is returned the param has no type-hint). This is where caching generated reflections becomes particularly important for any real-world use-case.
The code below is a severely stripped-down version of my own string-based recursive dependency injection container. It's a mixture of pseudo-code and real-code (if you were hoping for free code to copy/paste you're out of luck). You'll see that the code below matches the associative array keys of "definition" arrays to the parameter names in the constructor signature.
The real code can be found over at the relevant github project page.
class Provider {
private $definitions;
public function define($class, array $definition) {
$class = strtolower($class);
$this->definitions[$class] = $definition;
}
public function make($class, array $definition = null) {
$class = strtolower($class);
if (is_null($definition) && isset($this->definitions[$class])) {
$definition = $this->definitions[$class];
}
$reflClass = new ReflectionClass($class);
$instanceArgs = $this->buildNewInstanceArgs($reflClass);
return $reflClass->newInstanceArgs($instanceArgs);
}
private function buildNewInstanceArgs(
ReflectionClass $reflClass,
array $definition
) {
$instanceArgs = array();
$reflCtor = $reflClass->getConstructor();
// IF no constructor exists we're done and should just
// return a new instance of $class:
// return $this->make($reflClass->name);
// otherwise ...
$reflCtorParams = $reflCtor->getParameters();
foreach ($reflCtorParams as $ctorParam) {
if (isset($definition[$ctorParam->name])) {
$instanceArgs[] = $this->make($definition[$ctorParam->name]);
continue;
}
$typeHint = $this->getParameterTypeHint($ctorParam);
if ($typeHint && $this->isInstantiable($typeHint)) {
// The typehint is instantiable, go ahead and make a new
// instance of it
$instanceArgs[] = $this->make($typeHint);
} elseif ($typeHint) {
// The typehint is abstract or an interface. We can't
// proceed because we already know we don't have a
// definition telling us which class to instantiate
throw Exception;
} elseif ($ctorParam->isDefaultValueAvailable()) {
// No typehint, try to use the default parameter value
$instanceArgs[] = $ctorParam->getDefaultValue();
} else {
// If all else fails, try passing in a NULL or something
$instanceArgs[] = NULL;
}
}
return $instanceArgs;
}
private function getParameterTypeHint(ReflectionParameter $param) {
// ... see the note about retrieving parameter typehints
// in the exposition ...
}
private function isInstantiable($class) {
// determine if the class typehint is abstract/interface
// RTM on reflection for how to do this
}
}
I have a class that I am writing and I have a method that I would like to run once per initiation of the class. Normally this would go in the construct method, but I only need it to run when I call certain methods, not all.
How would you all recommend I accomplish this?
Create a private property $methodHasBeenRun which has a defualt value of FALSE, and set it to TRUE in the method. At the start of the method, do:
if ($this->methodHasBeenRun) return;
$this->methodHasBeenRun = TRUE;
You didn't specify exactly why you only want to run a given method once when certain methods are called, but I am going to make a guess that you're loading or initializing something (perhaps data that comes from a DB), and you don't need to waste cycles each time.
#DaveRandom provided a great answer that will work for sure. Here is another way you can do it:
class foo {
protected function loadOnce() {
// This will be initialied only once to NULL
static $cache = NULL;
// If the data === NULL, load it
if($cache === NULL) {
echo "loading data...\n";
$cache = array(
'key1' => 'key1 data',
'key2' => 'key2 data',
'key3' => 'key3 data'
);
}
// Return the data
return $cache;
}
// Use the data given a key
public function bar($key) {
$data = $this->loadOnce();
echo $data[$key] . "\n";
}
}
$obj = new foo();
// Notice "loading data" only prints one time
$obj->bar('key1');
$obj->bar('key2');
$obj->bar('key3');
The reason this works is that you declare your cache variable as static. There are several different ways to do this as well. You could make that a member variable of the class, etc.
I would recommend this version
class example {
function __construct($run_magic = false) {
if($run_magic == true) {
//Run your method which you want to call at initializing
}
//Your normale code
}
}
so if you do not want to run it create the class like
new example();
if you want
new example(true);
Static properties make testing hard as you probably know. Is there no way to reset all static properties of a particular class back to their initial state? Ideally this would not require custom code for each class, but could be used in a general way by inheritance, or from outside of the class completely.
Please do not reply with something like, "don't use static properties". Thanks.
Assuming you're using PHPUnit:
See the PHPUnit Manual section about global state. Static members are covered by this if you have PHP 5.3 or higher. Static members are not part of serialization (in case you wonder).
See as well #backupGlobals and #backupStaticAttributes
No. PHP does not preserve that information.
I was toying around with ReflectionClass and ::getDefaultProperties and ::getStaticProperties, but they only return the current state.
You will have to create an array with the default values, then manually foreach over them and reset your class attributes.
I couldn't find any way to include or require classes or functions many times without getting an error.
Anyway, if you need to replace functions inside an structure you should make an array/ArrayObject of lamdas/inline functions (like javascript objects)
When you re import the array it will back to the original state.
$Animal = array(
'eat' => function($food) {/*...*/},
'run' => function($to_place) {/*...*/}
);
$Animal['eat'] = function($food) {/* new way to eat */}
I also managed to reset the state of static attributes by using Reflections. For this approach you need to use a convention attribute naming for default value of each type.
class MyStaticHolder {
public static $x_array = array();
public static $x_num = 0;
public static $x_str = '';
}
//change values
MyStaticHolder::$x_array = array(1,2,4);
MyStaticHolder::$x_num = -1.4;
MyStaticHolder::$x_str = 'sample-text';
function reset_static($class_name) {
$z = new ReflectionClass($class_name);
$properties = $z->getDefaultProperties();
print_r($properties);
foreach ($properties as $property_name => $value) {
$sufix = end(explode('_',$property_name));
switch ($sufix) {
case 'array':
$class_name::$$property_name = array();
break;
case 'num':
$class_name::$$property_name = 0;
break;
case 'str':
$class_name::$$property_name = '';
break;
default:
$class_name::$$property_name = null;
break;
}
}
}
reset_static('MyStaticHolder');
What would be a good way (along with any pros and cons) of initializing an instance of a PHP class with another object of the same class (ideally in PHP 4.x)?
Here in initialize() is essentially what I'd like to be able to do (example is extremely simplified from my use-case, see below):
$product = new Product('Widget');
$product2 = new Product('Widget #2');
$product->initialize($product2);
echo $product->name; // echos "Widget #2"
class Product {
var $name;
function __constructor($name) {
$this->name = $name;
}
function initialize($product) {
// I know this cannot be done this way in PHP.
// What are the alternatives and their pros & cons?
$this = $product;
}
}
I know this may not be "good programming practice"; with 20+ years programming experience on other languages I know a bit about what's good and what's not. So hopefully we won't get hung up on if doing this makes sense or not. I have a use-case working with some open-source code that I can't change so please just bear with me on my need for it. I'm actually trying to create an OOP wrapper around some really ugly array code buried deep in the core of WordPress.
I'm trying to write it so in future versions they can move away from the ugly array-based code because everyone will be using the new API that otherwise fully encapsulated these nasty arrays. But to make it work elegantly I need to be able to do the above (in PHP 4.x) and I don't want to write code that just copies the properties.
Thanks in advance for your help.
UPDATE
Many of you are suggesting clone but unless I misunderstand that doesn't address the question. clone makes a copy; that's not the crux of the question. I'm instead trying to get the constructed object to "become" the object passed in. At this point I'm assuming there isn't a way to do that based on the fact that 0 out of 5 answers have suggested anything but I'll wait a bit longer before selecting a best in case it was simply that my questions was unclear.
In PHP 5, object cloning might be more relevant:
http://php.net/manual/en/language.oop5.cloning.php
You can define a special __clone method.
In PHP 4 and 5, you can copy properties via:
function copy($obj)
{
foreach (get_object_vars($obj) as $key => $val)
{
$this->$key = $val;
}
}
However, you wrote "I don't want to write code that just copies the properties," and I'm not exactly sure what you mean by that.
Preferred way of doing this is to use clone keyword and to implement appropriate __clone() method if needed as mentioned by other posters. Another trick way of doing this (con: slow, pros: can be stored, sent over network and works identical in php4/5) is to serialize an object and then unserialize to create new copies of it with identical variable values.
Example:
$productCopy = unserialize(serialize($product));
EDIT: Sorry, misunderstood what you were asking for. You will have to initialize variables of the object being constructed with passed in object's variables inside of the constructor. You can't return a reference to another object from the constructor.
Example:
public function __construct($name, $object = null) {
if($object) {
foreach(get_object_vars($object) as $k => $v) {
$this->$k = $v;
}
} else {
$this->name = $name;
}
}
class Product {
var $name;
function __construct($value) {
if (is_a($value, 'Product')) {
$this->name = $value->name;
} else {
$this->name = $value;
}
}
}
Similarly, you can use instanceof instead of is_a if you prefer (depending on your PHP version).
Now you can pass a Product instance OR a name to the construct.
$product = new Product('Something');
$clone = new Product($product);
This is the best way of doing it so far:
http://www.blrf.net/howto/51_PHP__How_to_control_object_instances_in_PHP_.html
Don't use "new", instead use a static function that returns the instance you want.
I have done the following:
class MyClass {
private static $_instances;
public static function get($id) {
if (!self::$_instances) self::$_instances = Array();
$class = get_called_class();
if (!array_key_exists($class, self::$_instances)) self::$_instances[$class] = Array();
if (!is_numeric($id) || $id == '') throw new Exception('Cannot instantiate a non-numeric ID.');
if (array_key_exists($id, self::$_instances[$class])) return self::$_instances[$class][$id];
else {
self::$_instances[$class][$id] = new static($id);
return self::$_instances[$class][$id];
}
}
function __construct($id=false) {
// new instance code...
// I use $id=false to create new a db table row not load an old one
}
}
Usage:
// New instance
$a = new MyClass();
$a = new MyClass;
// MyClass with $id = 1
$b = MyClass::get(1);
$c = MyClass::get(1);
$d = new MyClass(1);
$b and $c point to the same object, while $d is a new one.
Caveats:
Garbage collection will no longer apply as your instances are stored in a static array
You'll have to change your code to use MyClass::get
Notes in my code:
New instances are called with "new static" instead of "new self" to use late static bindings.
You can set your constructor to private. This will break all your old code if you use "new", but will ensure you don't get double instances or more. You'll have to change a bit in the get function's arguments and code to allow $id=false or $id=-1 or whatever.
maybe
$product = new Product('Widget');
$product2 = new Product(null, $product);
echo $product2->name; // echos "Widget #2"
class Product {
var $name;
function __constructor($name, $product = null) {
$this->name = !empty($name) ? $name : $product->name;
}
}
Adding another answer due to it being radically different.
$product = new Product('Widget');
$product2 = new Product('Widget #2');
$product =& $product2;
echo $product->name; // echos "Widget #2"
That should work.