Dynamic methods with parameters typing in PHP - php

I have the PHP legacy code below that intends to return the name of a method dynamically.
public function getMethod($fieldName){
//do stuff
return $methodName;
}
The returned method names are something like:
setClient
setName
setAge
All right here.
The problem is when I use these methods. I have a class named Model, that has the folowing method:
class Model {
public function find () {
$nameClass = get_called_class();
$instance = new $nameClass;
$result = $this->conn->select();//the select method returns a SQL SELECT from the database
if (isset($result[0])) {
foreach ($result[0] as $key => $val) {
$methodProp = $this->getMethod(key);
$instance->$methodProp($val);
}
} else {
throw new Exception('You have a error in your class');
}
}
}
Here is the var_dump($result) as requested:
array (size=1)
0 =>
object(stdClass)[6]
public 'id' => string '1' (length=1)
public 'id_erp' => string '0' (length=1)
public 'name' => string 'Derp' (length=18)
public 'email' => null
public 'type_ota' => null
public 'user' => string 'derp_derp' (length=7)
public 'password' => string '1234' (length=4)
public 'url_logo' => null
public 'status' => string '1' (length=1)
public 'date' => string '2015-06-08 14:41:50' (length=19)
Then I have some classes that extends this Model:
class Company extends Model {
public function setClient (Client $cli) {
//do stuff
}
public function setName($name) {
//do stuff
}
public function setAge($age) {
//do stuff
}
}
//here I use the class
$myCompany = new Company();
$myCompany->find();
The methods setName() and setAge() works fine, but setClient() returns
Catchable fatal error: Argument 1 passed to setClient() must be an instance of Client, string given
In short, how can I deal with dynamic methods and typing in PHP?
Looking for help, I found something about Reflection, but I never used this class before and the examples that I found didn't help me, although I think that it's the right way.
Has anyone had a similar experience?
UPDATE
I tried to put some code in my question to clarify my problem.

If I understand your question correctly, you want to check what typehint the method wants. I took this code snippet from the docs.
<?php
//Target our class
$reflector = new ReflectionClass('MyClass');
//Get the parameters of a method
$parameters = $reflector->getMethod('FireCannon')->getParameters();
//Loop through each parameter and get the type
foreach($parameters as $param)
{
//Before you call getClass() that class must be defined!
echo $param->getClass()->name;
}
To add the code above to your code. This would check if the parameter in the method is an object, if is an object it will check that the class is the same as the value given. If the parameter is not an object, you can either just call the method, or if you typehint your non-object parameters, you can check that they are equal as well. This code should definitely be modified and cleaned up, but I thought it would be enough to get my point across.
class Model {
public function find () {
$nameClass = get_called_class();
$instance = new $nameClass;
$result = $this->conn->select();//the select method returns a SQL SELECT from the database
if (!isset($result[0])) {
throw new Exception('You have a error in your class');
}
foreach ($result[0] as $key => $val) {
$methodProp = $this->getMethod($key);
$reflector = new ReflectionClass($nameClass);
$reflectorMethod = $reflector->getMethod($methodProp);
// Make sure method doesn't require more than one param,
// and it has defined at least one param.
if ($reflectorMethod->getNumberOfRequiredParameters() > 1 ||
$reflectorMethod->getNumberOfParameters() < 1
) {
// Throw error
}
//Get the parameters of a method
$parameters = $reflectorMethod->getParameters();
if (is_object($parameters[0])) {
if (!is_object($val) {
// Throw error
}
//Before you call get_class() that class must be defined!
//Use either class_exists($nameClass); or
//in_array($nameClass, get_declared_classes()); to check.
if ($val->getClass()->name !== get_class($parameters[0])) {
// Throw error
}
} else if (gettype($parameters[0]) !== gettype($val)) {
// Throw error
}
$instance->$methodProp($val);
}
}
}

Your problem is here
public function setClient (Client $cli) {
//do stuff
}
See how you have a class name next to the argument? That's called type hinting. It means that argument MUST be an instance of the class you specified or you'll get the error you posted. Somewhere in your code you're calling
$method($param);
And $param is not an instance of Client. So that is what you have to fix. Whatever calls setClient has to have an instance of Client.
$param = new Client();
$method($param);

Related

Wanna unit-test a function, but constructor gives error

I am trying to unit test a function which is in an entity class, and it is stored in my DB by the use of a constructor. Each time I am trying to test this function it is giving me that error
ArgumentCountError: Too few arguments to function App\Entity\Deal::__construct(), 0 passed in /var/www/html/casus/tests/dealsEntityFunctionsTest.php on line 10 and exactly 1 expected
It is obvious I think, but I am really new with unit testing and that stuff so I couldn't find the answer. Could you please help me?
My code is
class Deal
{
private bool $isNewToday
public function __construct($deal)
{
$this->isNewToday = $deal['is_new_today'];
}
public function getIsNewToday(): ?bool
{
return $this->isNewToday;
}
public function setIsNewToday(bool $isNewToday): self
{
$this->isNewToday = $isNewToday;
return $this;
}
}
And my unit test is
class test extends TestCase
{
public function testIsNewTodayIsTrue()
{
$deal = new Deal();
$deal->setIsForSale(true);
$this->assertTrue($deal->getIsForSale(), true);
}
}
As brombeer suggested, new Deal entity requires parameter.
This parameter looks like an array, with key 'is_new_today'. So, sth like this below should help with constructor error.
class test extends TestCase
{
public function testIsNewTodayIsTrue()
{
$deal = new Deal(['is_new_today' => true]);
$deal->setIsForSale(true);
$this->assertTrue($deal->getIsForSale(), true);
}
}
This has nothing to do with Unit Testing, or Symfony, or any of the other details you mentioned. You've defined something with a mandatory parameter, and then aren't passing that parameter.
Just like any function, the parameters to a constructor are mandatory unless you provide a default. And if you write code that assumes the parameter will have a particular format, you need to provide a value that meets that assumption.
So either pass the parameter every time you create the object, with whatever format the constructor expects:
$deal = new Deal(['is_new_today' => false]);
... or make it optional, and decide what should happen if it's not passed:
class Deal
{
private bool $isNewToday
public function __construct(?array $deal = null)
{
if ( isset($deal) ) {
$this->isNewToday = $deal['is_new_today'];
}
else {
$this->isNewToday = false;
}
}
}
Note that $isNewToday is defined as a non-nullable boolean, so you should always give it a value in the constructor, or an inline default, like private bool $isNewToday = false; Otherwise, you'll get "uninitialized value" errors if you try to read it. For that reason, the return type of ?bool on getIsNewToday() doesn't make sense - it can't return null, because $this->isNewToday can never be bool.

Formatting of Numbers with PDO

I have recently moved a large php application from using mssql_ functions to the PDO function using the mssql driver.
I wrote a simple library that allows drop in replacement. It all seems to work pretty well considering.
However one thing that is a bit annoying is default format of numbers and particularly numbers defined as money in the database.
Most of my smarty template pages previous simply output the number as it came from the database so someones balance might be show as
125.00
however since changing to PDO this is returned as
125.0000
This is a little annoying and off putting, but obviously not the end of the world.
My Question. Is there a workaround / trick / formatting Constant or method that I can use to get PDO to format values differently, or do I need to go an manually set the format for every number in every template throughout my app?
So basically, what I'd do is create models that represent a result-set for each table, and use PDO::FETCH_CLASS to load the data into instances of the corresponding class. For example:
class UserTable //extends AbstractTable <-- see below
{
protected $id = null;
protected $name = null;
protected $email = null;
protected $money = null;
}
Then add getters and setters that format/validate the data accordingly eg:
public function getMoney()
{
return sprintf('%.2f', $this->money);//check if not null first, obviously
}
Next, have an abstract class for these models, and implement the ArrayAccess interface in there. For example, using a simple mapping array:
protected $getterMap = [
'email' => 'getEmail',
'id' => 'getId',
'money' => 'getMoney',
];
Define a tailor-made map in each child, then have the abstract class use it like so:
//in abstract class AbstracTable implements ArrayAccess
public function offsetGet($offset)
{
if (!isset($this->getterMap[$offset])) {
throw new RuntimeException(
sprintf('%s not a member of %s', $offset, get_class($this));
);
}
$getter = $this->getterMap[$offset];
return $this->{$getter}();//use the getter, it formats the data!
}
Do something similar for all 4 methods in the interface, and now you can use this:
$row = $stmt->fetch(PDO::FETCH_CLASS, 'User');
$row['money'];//will call getMoney, and return the formatted number
A more complete example:
abstract class AbstractTable implements ArrayAccess
{
protected $id = null;//very likely to be defined in all tables
protected $getterMap = [
'id' => 'getId',
];
protected $setterMap = [
'id' => 'setId',
];
//force child classes to define a constructor, which sets up the getter/setter maps
abstract public function __construct();
public offsetExists($offset)
{
return isset($this->getterMap[$offset]);
//optionally, check if value if not null: isset($arr['keyWithNullVal']) returns null, too:
return isset($this->getterMap[$offset]) && $this->{$offset} !== null;
}
public offsetGet ( mixed $offset )
{
if (!isset($this->getterMap[$offset])) {
throw new RuntimeException('member does not exist');
}
$getter = $this->getterMap[$offset];
return $this->{$getter}();
}
public offsetSet($offset, $value )
{
if (!isset($this->setterMap[$offset])) {
throw new RuntimeException('Trying to set non-existing member');
}
$setter = $this->setterMap[$offset];
$this->{$setter}($value);
}
public offsetUnset ($offset)
{
//same as setter, but call:
//or just leave blank
$this->{$setter}(null);
}
}
class UserTable extends AbstractTable
{
//protected $id = null; in parent already
protected $name = null;
protected $email = null;
protected $money = null;
public function __construct()
{
$fields = [
'name' => 'etName',
'email' => 'etEmail',
'money' => 'etMoney',
];
foreach ($fields as $name => $method) {
$this->getterMap[$name] = 'g' . $method;
$this->setterMap[$name] = 's' . $method;
}
}
}
Obviously, you'll have to write the getters and setters for all the fields. Not to worry, though: most IDE's will helpfully generate the getters and setters for predefined properties at the click of a button

phpspec - method returns object instead of string

I'm still fresh in phpspec but usually I'm finding a solution when I struggle with something but this one is tough.
I've tried many different approaches and I haven't found a solution. I'm using Symfony2.
I have a class that I want to test:
class MyClass
{
public function getDataForChildren(MyObject $object)
{
foreach ($object->getChildren() as $child) {
$query = \json_decode($child->getJsonQuery(), true);
$data = $this->someFetcher->getData($query);
$child->setData($data);
}
return $object;
}
}
And here's how look my spec class:
class MyClassSpec
{
function let(SomeFetcher $someFetcher)
{
$this->beConstructedWith($someFetcher);
}
function it_is_initializable()
{
$this->shouldHaveType('MyClass');
}
function it_should_get_data_for_children_and_return_object(
MyClass $object,
MyClass $child, // it means that MyClass has a self-reference to MyClass
$someFetcher
)
{
$query = '{"id":1}';
$returnCollection = new ArrayCollection(array($child));
$object->getChildren()->shouldBeCalled()->willReturn($returnCollection);
$child->getJsonQuery()->shouldBeCalled()->willReturn($query);
$someFetcher->getData($query)->shouldBeCalled();
$this->getDataForChildren($object);
}
}
And after running phpspec I'm getting this error:
warning: json_decode() expects parameter 1 to be string, object given in
I have no idea how to solve this problem. If anyone has a clue, please help.
This is a common stumbling block with PhpSpec, the declaration:
MyClass $child
means that a Collaborator object of $child will be set up with the same interface of MyClass.
When child->getJsonQuery() is called in the SUT (class you're testing), it will return a MethodProphecy not the string you expect it to return.
What you want to say is that your ArrayCollection will contain not $child itself (which is a Collaborator object), but the real object that the collaborator is wrapped around. You do it like this:
$returnCollection = new ArrayCollection(array($child->getWrappedObject()));
In addition, you should not be using (i.e. is is superfluous) both
shouldBeCalled() and willReturn() on the same Collaborator, one or the
other is sufficient. If you've specified what the collabrator will
return, it is clear that it is going to be called witin the SUT.
shouldBeCalled() should be used in the "assert" part of the test in
order to confirm that the Collaborator was called with the expected
arguments, or at the right time.
Your final SUT and spec should look something like this:
class MyClass
{
/**
* #var SomeFetcher
*/
private $someFetcher;
public function getDataForChildren(MyObject $object)
{
foreach ($object->getChildren() as $child) {
$query = \json_decode($child->getJsonQuery(), true);
$data = $this->someFetcher->getData($query);
$child->setData($data);
}
return $object;
}
public function getJsonQuery()
{
}
public function setData()
{
}
public function __construct(SomeFetcher $someFetcher)
{
$this->someFetcher = $someFetcher;
}
}
class MyClassSpec extends ObjectBehavior
{
function let(SomeFetcher $someFetcher)
{
$this->beConstructedWith($someFetcher);
}
function it_should_get_data_for_children_and_return_object(
MyObject $object,
MyClass $child, // it means that MyClass has a self-reference to MyClass
SomeFetcher $someFetcher
)
{
$query = '{"id":1}';
$returnCollection = new ArrayCollection(array($child->getWrappedObject()));
$object->getChildren()->willReturn($returnCollection);
$child->getJsonQuery()->willReturn($query);
$child->setData(Argument::any())->shouldBeCalled();
$someFetcher->getData(array('id' => 1))->shouldBeCalled();
$this->getDataForChildren($object);
}
}
Also, the line
$query = \json_decode($child->getJsonQuery(), true);
Will produce a associated array in $query, i.e. array('id' => 1) (this is what the second 'true' argument to json_encode stipulates), therefore you'd expect $someFetcher->getData() to be called with the latter, hence:
$someFetcher->getData(array('id' => 1))->shouldBeCalled();

PHP Fatal Error: Cannot use object of type DataAccess as array

I cannot figure out why I am getting the following error in PHP:
Fatal error: Cannot use object of type DataAccess as array in /filename on line 16.
Here is the relevant code for the file:
class StandardContext implements IStandardContext
{
private $dataAccess;
// (CON|DE)STRUCTORS
function __construct($config)
{
$this->dataAccess = new DataAccess($config['db']); //this is line 16
}
$config refers to the following:
$config = require(dirname(__FILE__)./*truncated*/.'Config.php');
Here is the relevant code for Config.php:
return array(
// Database connection parameters
'db' => array(
'host' => 'localhost',
'name' => 'visum',
'user' => 'root',
'password' => ''
)
);
Here is the relevant code for the DataAccess object:
class DataAccess
{
private $link;
private $db;
function __construct($dbConfig)
{
$this->link = mysql_connect( $dbConfig['host'], $dbConfig['user'], $dbConfig['password'] ) or die(mysql_error());
$this->db = $dbConfig['name'];
mysql_select_db($this->db) or die(mysql_error());
}
Any help would be greatly appreciate, I am fairly new to PHP and am absolutely stumped.
Edit: BTW, I have included the following code to test StandardContext, which actually works (ie. it allows me to make changes to my database farther down than I have shown)
class StandardContext_index_returns_defined_list implements ITest
{
private $dataAccess;
function __construct($config)
{
$this->dataAccess = new DataAccess($config['db']);
}
It's almost like you are trying to use a singleton pattern, but for every StandardContext object you instantiate, you are passing in database parameters (via $config array). I think what's happening is that you are passing the $config array more than once, after the first pass the $config is no longer an array, but an instance of the DataAccess class, which is why you are getting that error. You can try the following:
class StandardContext implements IStandardContext
{
private $dataAccess;
// (CON|DE)STRUCTORS
function __construct($config)
{
if ($config instanceof DataAccess) {
$this->dataAccess = $config;
} elseif ((is_array($config)) && (array_key_exists('db', $config))) {
$this->dataAccess = new DataAccess($config['db']);
} else {
throw new Exception('Unable to initialize $this->dataAccess');
}
}
this is problem with your
private $dataAccess;
check the array object here
http://www.php.net/manual/en/class.arrayobject.php
whenever you declare outside a method inside class, it will consider as Object , so you have to declare inside method or declare as method itself else remove implements from your class.
your $dataAccess is an Object , because you declare it outside the method and your new DataAccess($config['db']) will return an arrayObject because you implements that, so it is trying to convert from Object to arrayObject leads an error

PHP constructor to return a NULL

I have this code. Is it possible for a User object constructor to somehow fail so that $this->LoggedUser is assigned a NULL value and the object is freed after constructor returns?
$this->LoggedUser = NULL;
if ($_SESSION['verbiste_user'] != false)
$this->LoggedUser = new User($_SESSION['verbiste_user']);
Assuming you're using PHP 5, you can throw an exception in the constructor:
class NotFoundException extends Exception {}
class User {
public function __construct($id) {
if (!$this->loadById($id)) {
throw new NotFoundException();
}
}
}
$this->LoggedUser = NULL;
if ($_SESSION['verbiste_user'] != false) {
try {
$this->LoggedUser = new User($_SESSION['verbiste_user']);
} catch (NotFoundException $e) {}
}
For clarity, you could wrap this in a static factory method:
class User {
public static function load($id) {
try {
return new User($id);
} catch (NotFoundException $unfe) {
return null;
}
}
// class body here...
}
$this->LoggedUser = NULL;
if ($_SESSION['verbiste_user'] != false)
$this->LoggedUser = User::load($_SESSION['verbiste_user']);
As an aside, some versions of PHP 4 allowed you to set $this to NULL inside the constructor but I don't think was ever officially sanctioned and the 'feature' was eventually removed.
AFAIK this can't be done, new will always return an instance of the object.
What I usually do to work around this is:
Adding a ->valid boolean flag to the object that determines whether an object was successfully loaded or not. The constructor will then set the flag
Creating a wrapper function that executes the new command, returns the new object on success, or on failure destroys it and returns false
-
function get_car($model)
{
$car = new Car($model);
if ($car->valid === true) return $car; else return false;
}
I'd be interested to hear about alternative approaches, but I don't know any.
Consider it this way. When you use new, you get a new object. Period. What you're doing is you have a function that searches for an existing user, and returns it when found. The best thing to express this is probably a static class function such as User::findUser(). This is also extensible to when you're deriving your classes from a base class.
A factory might be useful here:
class UserFactory
{
static public function create( $id )
{
return (
filter_var(
$id,
FILTER_VALIDATE_INT,
[ 'options' => [ 'min_range' => 1, ] ]
)
? new User( $id )
: null
);
}
}
When a constructor fails for some unknown reason, it won't return a NULL value or FALSE but it throws an exception. As with everything with PHP5. If you don't handle the exception then the script will stop executing with an Uncaught Exception error.
maybe something like this:
class CantCreateException extends Exception{
}
class SomeClass {
public function __construct() {
if (something_bad_happens) {
throw ( new CantCreateException());
}
}
}
try{
$obj = new SomeClass();
}
catch(CantCreateException $e){
$obj = null;
}
if($obj===null) echo "couldn't create object";
//jaz303 stole my idea an wrap it into a static method

Categories