What could be the reason for having this:
public function __construct($host, $port, $timeout = 5){
$errnum = 0;
$errstr = '';
Instead of this:
public function __construct($host, $port, $errnum = 0, $errstr = '', $timeout = 5){
?
Why some are params and others aren't ?
Thanks a lot,
MEM
A function definition defines a contract between the function itself and the code that calls it.
A variable should only be a parameter if the caller should specify it's value. Otherwise if a variable is only used internally by the function, there is no need to specify it as a parameter.
the errors are set by the function, and there is no point passing those in
If they would be params, the user could pass them in during the creation of the object. A call like
$a = new MyObject($myhost, $myport, 40000, 'Failed.', $mytimeout);
would initialize your object with an error already in its memory... In the case of an error number or string, that is rather unwanted. The user shouldn't be able to poke a random error into your object.
Normally you define a function in a way that it only accepts parameters/data that it definitely needs in order to run.
In you example, $errnum and $errstr seem to be variables that the function uses internally. If you design that function you have to decide whether you want to give the user the possibility to override those or not.
Maybe you want to call the constructor with more than 3 parameters, depending on what the constructor/class do. The parameter list is not the place to initialize local variables. Check the API of the class you are reading what the parameters are for (looks like they are for the fsockopen function, so read this documentation first).
$timeout is a default parameter that can be overwritten when calling the function.
$errnum and $errstr cannot be overwritten when calling the function.
Observe:
public function goodConstruct($host, $port, $timeout = 5){
$errnum = 0;
$errstr = '';
}
goodConstruct('hostname',8443,60);
By doing this, I can overwrite the default timeout.
public function badConstruct($host, $port, $errnum = 0, $errstr = '', $timeout = 5)
{
//code
}
badConstruct('hostname',8443,99,'hey look at this silly error!!!!',900);
Now I can also overwrite the error code (assuming that's the purpose of errnum, it's even worse if that's some sort of a counter) and the error string. Do you really want to be able to control this from your function call? Probably not... I assume that you'd want that to be fixed.
Related
I have the following class I would like to cover with some tests:
namespace Nietonfir\RaygunBundle\Services;
use Raygun4php\RaygunClient;
class Client extends RaygunClient implements NietonfirRaygunClient
{
private $defaultTags = null;
protected function mergeTags($tags)
{
if (is_array($this->defaultTags)) {
if (is_array($tags)) {
$tags = array_merge($this->defaultTags, $tags);
} else {
$tags = $this->defaultTags;
}
}
return $tags;
}
public function setDefaultTags(array $tags)
{
if (0 == count($tags)) {
$this->defaultTags = null;
}
$this->defaultTags = $tags;
}
public function setVersion($version)
{
parent::SetVersion($version);
}
public function sendException($exception, $tags = null, $userCustomData = null, $timestamp = null)
{
$tags = $this->mergeTags($tags);
parent::SendException($exception, $tags, $userCustomData, $timestamp);
}
public function sendError($errno, $errstr, $errfile, $errline, $tags = null, $userCustomData = null, $timestamp = null)
{
$tags = $this->mergeTags($tags);
return parent::SendError($errno, $errstr, $errfile, $errline, $tags, $userCustomData, $timestamp);
}
}
In specific I would like to test that sendException calls parent::SendException with the right $tags parameter. The problem of course is mocking the parent in some way, but I think it's hardly impossible. I was thinking of refactoring the class in order to make Client point to an instance of RaygunClient by means of an attribute. That way, it could be testable, but I'm not sure it's a nifty way to do that. After all, what I want is slightly changing the original parent class calls with normalized arguments.
Any piece of suggestion is more than welcome.
I had this as a comment, but it works as an answer and possible solution for you.
You clarified in the comments that the method you want to be called in your test case will send a cURL request. You want to avoid actually sending that request out during your test, since what you are testing is not PHP's cURL library. If your cURL call is within that method you should probably extract it to another class.
You will then be able to mock that class in your test, inject it in to your actual system that you are trying to test, and assert that when that method is called to send the cURL request the parameters in the request match what you should expect in your test case. It shouldn't matter to your test how those parameters show up the way that they do, but I am assuming that they will be different in some detectable ('assertable') way when funnelled through your parent class' method.
Changes to your system down the road that break this functionality (by supplying the wrong parameters to the cURL request) would then be detected.
Bear with me please as I'm quite new to the OOP concept so I might just be way wrong in my thinking here.
I'm developing a class for some functionality I use quite often and I would like it to be configurable in any new project on initialization. The caveat here is that I'd like to set certain default variables and allow them to stay un-configured if the defaults are alright. Here is a bit of code to try to make the concept a bit clearer.
class someClass{
// Setting parameter defaults
private $param_a = 60;
private $param_b = 100;
/*
* The construct function. What I'd like to do here is make the param_a and param_b optional,
* i.e if it doesn't get set on initialization it takes the defaults from the class.
*/
function __construct($param_a, $param_b, $foo){
// do something ...
}
}
$foo = "some value";
// init example using defaults
$someclass = new someClass($foo); // $param_a and $param_b should be 60 and 100 respectively
// init example using custom options
$someclass = new someClass(40, 110, $foo);
Am I going in the right direction as far as how to set up class configuration? If so, how do I make param_a and param_b optional?
function __construct($foo, $param_a = 60, $param_b = 100){
// do something ...
}
You could supply required method arguments first, and then ones with default parameters afterwards, making them optional.
Then assign these to the class variables inside the constructor.
Another way would be to use func_get_args() and parse this.
You could just make the constructor take a general $args argument and merge it with an array of defaults:
public function __construct($args = array()) {
$args = array_merge(array(
'param_a' => 60,
'param_b' => 100,
'foo' => null
), $args);
foreach($args as $key => $val) {
$this->$key = $val;
}
}
i am wondering is it possible to restrict php variable to accept only certain type of variable it is defined to accept.
e-g if we see in C#
public int variable = 20;
public string variable2 = "Hello World";
so what would it be like in php if i want to restrict type for variable.
public int $variable = 20;
so that if i try to assign string to integer variable i get the error.
public int $varaible = "Hello World"; //As type is integer, it should throw error.
is there such thing defining types in PHP for variables before assigning values to it??
TL;DR Not directly, no. PHP is not strictly-typed. There are, however, a few workarounds that may work for you in the context of function parameters or properties of classes.
Long answer: PHP is not a strictly-typed language, but loosely-typed. You can give a variable any value you want, regardless of how it was initialized. So, you can't simply type something like int $myVar and expect $myVar = "foo"; to throw an error. But PHP does offer a few handy features to get you to the same end when dealing with function parameters or properties of a class.
Option 1: Type hints
You can use a "type hint" for function parameters:
class SomeClass
{
/* code here */
}
function foo(SomeClass $data)
{
/* code here */
}
foo() will only accept parameters of type SomeClass. Passing it, say, an int will throw a fatal error. This doesn't work in PHP < 7 if the parameters are intended to be base types, like int, string, etc., so you can't do function foo(int $data) {...}. That said, there are a few libraries out there that attempt to force it to work at the expense of a little speed. Also, PHP 7 adds a lot of support for this kind of thing, as does the Hack language based on PHP.
Pros:
Easy
Intuitive
Cons:
Only works for program-defined classes
Unavailable for base types
Option 2: Getters and Setters
You can also use getters and setters, like so:
class SomeClass
{
private $foo = 0;
function setFoo($val = 0)
{
// force it to be an int
if (is_integer($val) {
$this->foo = $val;
} else {
// throw an error, raise an exception, or otherwise respond
}
}
}
Pros:
Relatively easy
Relatively intuitive
Cons:
Only works in program-defined classes
Unavailable for base types
Requires lots of code
Option 3: Magic Methods
This method is my favorite, but also the most complicated. Use the __set() magic method to deal with class properties.
class MyClass {
private $type = 0; // we will force this to be an int
private $string = ''; // we will force this to be a string
private $arr = array(); // we will force this to be an array
private $percent = 0; // we will force this to be a float in the range 0..100
function __set($name, $value) {
switch ($name) {
case "type":
$valid = is_integer($value);
break;
case "string":
$valid = is_string($value);
break;
case "arr":
$valid = is_array($value);
break;
case "percent":
$valid = is_float($value) && $value >= 0 && $value <= 100;
break;
default:
$valid = true; // allow all other attempts to set values (or make this false to deny them)
}
if ($valid) {
$this->{$name} = $value;
// just for demonstration
echo "pass: Set \$this->$name = ";
var_dump($value);
} else {
// throw an error, raise an exception, or otherwise respond
// just for demonstration
echo "FAIL: Cannot set \$this->$name = ";
var_dump($value);
}
}
}
$myObject = new MyClass();
$myObject->type = 1; // okay
$myObject->type = "123"; // fail
$myObject->string = 1; // fail
$myObject->string = "123"; // okay
$myObject->arr = 1; // fail
$myObject->arr = "123"; // fail
$myObject->arr = array("123"); // okay
$myObject->percent = 25.6; // okay
$myObject->percent = "123"; // fail
$myObject->percent = array("123"); // fail
$myObject->percent = 123456; // fail
Pros:
Relatively easy
Intuitive
Extremely powerful: one setter to rule them all
Cons:
Only works in program-defined classes
Unavailable for base types
Requires lots of switching or if/else logic
Can cause problems with IDEs not auto-completing property types correctly
Here's a demo of this approach.
Closing Thoughts
Finally, if you're using an IDE like PHPStorm, don't forget about PHPDoc type hints:
/* #var integer */
$foo = 0; // will result in warnings if the IDE is configured properly and you try to do something like substr($foo, 1, 4);
And if you really want to go hard core, you can do strong typing using Hack, at the expense of making your code less portable and less compatible (for now) with major IDEs.
Of course, none of these is a substitute for explicitly validating user input and thoroughly testing the application's response to unexpected input types.
No. PHP is not a strictly typed language. You can however use type hints in functions and methods.
If class or interface is specified as type hint then all its children or implementations are allowed too.
Type hints can not be used with scalar types such as int or string. Resources and Traits are not allowed either.
The Scalar types being:
string
bool
int
float
Examples:
function (array $theArr) {
// body
}
class X {
public function __construct(SomeOtherClass $arg) {
// body
}
public function setFoo(Foo $foo) {
}
}
See the manual for more specifics: http://php.net/manual/en/language.oop5.typehinting.php
You have to made it by your own hands, example :
function setInt(&$var, $value) {
if(!is_integer($value) {
throw new Exception("Integer wanted " . gettype($value) . " received");
}
$var = $value;
}
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 need to call a function using the Reflection API. The function has optional parameters, and I need to invoke it providing just some of them.
For example, I have this function:
public function doSomething($firstParam, $secondParam = "default", $thirdParam = "default)
And I'm using invokeArgs() to invoke doSomething(), passing an array of values representing the arguments omitting to set a value to the optional $secondParam:
$parameters = array("firstParam"=>"value", "thirdParam"=>"thirdValue");
$reflectedDoSomething->invokeArgs($instance, $parameters);
What happens here is that invokeArgs() invokes the method setting its parameters in a row, without skipping the $secondParam, that now is valued "thirdValue" and omitting the $thirdParam. And that's logically correct. The method is invoked like doSomething("value", "thirdValue").
What I'd like to do here is to force the $secondParam to use its default value. Setting "secondParam" => null in the $parameters array is not a solution because null is a value.
Is it possibile using Reflection?
Thanks
See the first comment on the docs for invokeArgs().
So, just run the params through a simple algorithm (you could create a function for wrapping this if you wanted to):
$reflection = new ReflectionMethod($obj, $method);
$pass = array();
foreach($reflection->getParameters() as $param) {
/* #var $param ReflectionParameter */
if(isset($args[$param->getName()])) {
$pass[] = $args[$param->getName()];
} else {
$pass[] = $param->getDefaultValue();
}
}
$reflection->invokeArgs($obj, $pass);
How about not setting the associative name secondParam, but rather putting a null as a second second parameter.
$parameters = array("firstParam"=>"value", null, "thirdParam"=>"thirdValue");