I've been busy trying to create my own framework (to become more experienced in this area), and stumbled on an error I couldn't fix by searching google... wow...
I want to get data from a database, placed in an object / class. I've done it before, in a different way I learned at school, but I wanted to tweak it and make it more dynamic so I could use it in my framework.
The problem I stumbled on is the following:
SQLSTATE[HY000]: General error: could not call class constructor on line 96
This is the function in my database class:
public function getObject($query, $classRootPath)
{
try {
//Check if slashes are double already and make them if not
if(!strpos($classRootPath, "\\\\")) {
$classRootPath = str_replace("\\","\\\\",$classRootPath);
}
$statement = $this->pdo->prepare($query);
$statement->execute(\PDO::FETCH_CLASS, "Campers\\Camper"); // I want this path to be $classRootPath once it is working with this dummy data
return $statement->fetchAll();
// return $this->pdo->query($query)->fetchAll(\PDO::FETCH_CLASS, "Campers\\Camper");
} catch (\PDOException $e) {
throw new \Exception("DB receive object failed: " . $e->getMessage());
}
}
This function is nested in Database and the class is called Database / Database.php
The following class is nested in Campers and is called Camper.php
class Camper {
public $ID, $date, $camperID;
public function __construct($ID, $date, $camperID)
{
$this->ID = $ID;
$this->date = $date;
$this->camperID = $camperID;
}
}
The only reason I can think of this is not working, is that the call "Campers\\Camper" is calling on top of Database, but I don't know how to escape that. I tried with ..\ but I got errors back, and this is the closest I can get. Here it can find the class though, but it can't find the constructor of Camper...
I've tested if my db class / connection works, so that's not the fault.
The structure of my table matches my Campers class constructor.
From the PSR-4 spec:
The terminating class name corresponds to a file name ending in .php. The file name MUST match the case of the terminating class name.
You likely can't instantiate that Camper class as-is anyway. PSR-4 expects your filename to match the class. It should be located in framework/Campers/Camper.php.
This error implies more than been unable to call the constructor, it is also used to indicate than an error occurred while calling it.
In my case, an Exception was been thrown inside de constructor. If you don't print/log the stacktrace, you could easily miss it.
Enjoy!
:)
I had the same issue in at least 3 cases.
Case 1: You select something from the database that can contain a NULL value.
SELECT name FROM tableX;
In that case I do the select in that way:
SELECT IFNULL(name,'') AS name FROM tableX;
where name is a field in your class.
Case 2: You select something that is not a field in your class
class Example {
public string $name = '';
}
Then the following query will fail as id is not declared in your class
SELECT id, name FROM tableX;
case3:
your field in the class isn't initialised
class Example {
public string $name;
}
SELECT name FROM tableX;
can be solved by either initialise the field
class Example {
public string $name = '';
}
or using a constructor to declare it
BR
Related
i create a Depot class. when i create object from this class i use find method for find a Special item with id.
after that i cant call any other method.
I do not use Laravel
// index.php file
$depot = new Depot();
$depot = $depot->find(2);
var_dump($depot->hi());
Fatal error: Uncaught Error: Call to undefined method stdClass::hi()
hi method is for test.
// model.php file
class Model {
// ...
public function find(int $id)
{
$statement = $this->pdo->prepare("select * from {$this->table} where id = :id");
$statement->execute(compact('id'));
$obj = $statement->fetch(PDO::FETCH_OBJ);
return $obj;
}
}
class Depot extends Model {
//...
public function hi()
{
echo "hi";
}
}
With this line:
$depot = $depot->find(2);
you're overwriting the variable $depot, representing your object, with the result of your query. The object returned (unsurprisingly) doesn't contain a function called hi().
I don't know if this was just a typo, but if not, it's generally a sign of poor code quality if you re-use the same variable to contain two completely different things. It leads to maintenance and readability issues, and often causes errors further down the line, such as this one, where you mistakenly assume the variable still has its original content. Weakly-typed languages such as PHP are especially vulnerable to this kind of mistake. The easiest thing is to just make a rule never to do it.
Assigning the result to a different variable, e.g.
$depot = new Depot();
$findResult = $depot->find(2);
$depot->hi();
will fix the issue.
(Also the var_dump() was unnecessary since hi() already contains an echo.)
try this
$depot = new Depot();
$depotDb = $depot->find(2);
var_dump($depot->hi());
I have a constructor that asks for a type of class, but it doesn't define that as a type hint. You are able to pass anything you want to it, and it will accept it. Is there a way to pass a class type to the constructor, and in the add() method it only accepts that type?
Currently what I have, is the ability to pass anything to the constructor such as an int, string, bool, etc. Is there a way to make it so that the constructor only accepts class types?
class Main{
protected $items = [];
protected $type = '';
public function __construct($type){
$this->type = $type;
}
public function add($object){
if($object instanceof $this->type){
$this->items[] = $object;
}
}
}
class Test{}
class Awesome{}
$main1 = new Main(Test::class);
$main2 = new Main(Awesome::class);
// Successful:
$main1->add(new Test());
// Fail:
$main1->add(new Awesome());
// Successful:
$main2->add(new Awesome());
// Fail:
$main2->add(new Test());
If I were to do it in C# it would look something like this:
Main main1 = new Main<Test>();
Main main2 = new Main<Awesome>();
Basically it says that add() will only allow instances of Test. Is there a way to do some
Php doesn't support template like declarations like e.g. c++.
The best way you may be able to achive this is by passing a lambda which then in return gets used in order to validate the passed parameter in add.
<?php
class Test {
private $validator = null;
public function __construct($validator) {
$this->validator = $validator;
}
public function add($value) {
$func = $this->validator;
$validated = $func($value);
echo $validated ? 'OK' : 'NG';
}
}
$obj = new Test(function($value) {
return is_int($value);
});
$obj->add(11);
$obj->add('string');
Another possibility would be to pass the type e.g. "ClassName" in your constructor and use get_class() and gettype() for the validation.
In the future there may be smarter solutions since you'll be able to write anonymous classes but I haven't really thought about that but in the end they would work similarly to lambdas.
Basically it says that add() will only allow instances of Test.
It's possible to achieve this in PHP by simply adding the type before the argument name in the function definition (similar with C/C++/C# types):
class Main {
protected $items = [];
public function add(Test $object) {
$this->items[] = $object;
}
}
PHP 5 accepts classes, interfaces, array and callable as type hints. If Test is a class then Main::add() accepts objects of class Test and its children. If Test is an interface, then the method Main::add() accepts objects that implement Test or one of its children.
PHP 7 (coming soon to a server near you) introduces type hinting for scalar types too.
PHP does not support anything similar with C++ templates or C# generics. If you want to create a class that works with objects of type A and another class that has identical behaviour but works with objects of type B you have several options but none of them is as elegant as the templates/generics:
Create two classes having identical behaviour, one for objects of type A, another for objects of type B; use different type hints (A and B) in the arguments lists of the methods of the two classes to enforce the separation - not scalable;
Something similar to your code, use the allowed class name as a string property and check it on any operation; you can also validate the argument of the constructor using class_exists() - the code becomes cluttered with tests and less readable;
Use OOP polymorphism; extend both A and B from the same class T or, even better, make A and B implement the same interface I. A PHP interface can be empty, it doesn't need to declare anything; empty interfaces used just for type hinting are common practice in PHP.
Then write a single class Main and use I as type hint for all its methods that accept objects. It will accept objects of both types A and B but if you also declare functions in I (and implement them in A and B, of course) then use them in Main you can be sure nothing breaks (I becomes a contract between Main and the objects its accepts as arguments for its methods).
I would choose option #3 because it gets the most help from the interpreter; it verifies the type of the arguments on each function call that has type hints and triggers a recoverable fatal error (in PHP 5) or throws an exception (in PHP 7).
Also some IDEs and static code analysis tools can validate the calls without running the code and help you fix it.
Is there a way to make it so that the constructor only accepts class
types?
Nope!
It is not possible in PHP. Not like C#, at least.
You need either set a type hint or set any types.
However, there's a closer solution in order to accept only class when instancing a class: Using ReflectionClass!
class Main {
protected $items = [];
protected $type = null;
public function __construct($type) {
$reflector = new ReflectionClass($type);
$this->type = $reflector->getName(); # or: $this->type = $type;
}
public function add($object) {
if($object instanceof $this->type) {
$this->items[] = $object;
}
}
}
As ReflectionClass contructor argument only accpets a string containing the name of the class to reflect, you can take advantage that, so passing scalars strings will cause an exception.
$main = new Main(Test::class); # Okay!
$main = new Main('Test'); # Okay!
However
$main = new Main('bool');
// Results
# PHP Fatal error: Uncaught exception 'ReflectionException'
# with message 'Class bool does not exist' in ...
Change your constructor to this:
public function __construct(Type $type){
$this->type = $type;
}
This is based on the assumption that $type is an instance of Type.
Hi world genius of programming. I am quite newbie in PDO and OOP, Please understand.
I try to do the most simple thing in the world - get data from a table in MySQL.
I want to:
1) SELECT * from ... it's about 20 fields.
2) To get an array of object with 4-6 of properties.
3) I want to use fetchAll and FETCH_CLASS...
PDOStatement PDO::query ( string $statement , int $PDO::FETCH_CLASS , string $classname , array $ctorargs )
I've found that we can pass an array of argument but can't implement it.
So what am I doing?
class handler{
connection etc..
public $params = array('surname','id','country','display' );
return $stmt->fetchAll(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'person',$this->params);
class person {
public $surname=null;
public $id=null;
public $country=null;
public $status=null;
and then
__construct ()
i will not put it - cause i ve got 50 variants of them(((
}
So, I need to filter options from 20 fields fetching a class but not in SELECT mode instead of *...
Is it possible?
I know that you are genius!
Forgive for newbieness
UPDATE
function __construct($surname,$id,$country,$display) {
$this->surname=$surname;
$this->country=$country;
$id->id->$id
// that the only i need in this oblject
}
function __construct() {
$arg=array('surname','id');
foreach ($arg as $val) {
$this->{$val}=$$val;
}
}
it seems it maybe the next.. not construct function that will filter properties...
UPDATE
I tried solutions as #GolezTrol kindly proposed.
Solution 1 is arguing for... Notice: Undefined property: Person::$_attributes in
if i make
class Entity {
public $_attributes;
function __construct() { ....
or
class Person extends Entity {
public $_attributes;
}
it works.. but i get an object...
[0] => Person Object
(
[_attributes] => Array
(
[0] => surname
[1] => id
[2] => country
[3] => status
)
[id] => 298
.. it's not good(
I think you mean that you want to load only the properties that you specified instead of all values that were returned from the query. Your attempt is to do that by passing the desired field names to the constructor.
Solution 1: Just specify the array of properties and block the rest
Your way might just work, if you get a little help from the __set magic method. Using func_get_args() you can get all the arguments of a function (the constructor in this case) into an array. This way, you get the array of field names that you passed to fetch_all.
The magic setter only sets the properties if they exist in the array that was given to the constructor, so essentially it filters out all fields you don't want.
Advantage: easy. No specific implementation needed in descendant classes. You could just use Entity as a class for all entities.
Disadvantage: magic setter is called for every property and calls in_array this may be slow. fetch_all is determining which fields to read, while maybe this should be the class's responsibility.
class Entity {
function __construct() {
$this->_attributes = func_get_args();
}
function __set($prop, $value) {
if (in_array($prop, $this->_attributes)) {
$this->$prop = $value;
}
}
}
// If you would need a descendant class to introduce methods, you can..
class Person extends Entity {
}
$stmt->fetchAll(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'Person', array('id', 'surname', 'gender'))
Solution 2: block all properties that don't exist
Similar solution, but much cleaner, I think. Implement the magic setter and make it do... nothing. It will be called for properties that don't exist and only for properties that don't exist. So in Person you just declare whatever values you want to read. All other properties will be directed to the empty __set method so they are implicitly ignored.
Advantage: Still easy. Hardly any implementation. You can put the empty method in a base class or just implement it in Person and every other class you have. You just declare the properties in Person. You don't even need to specify the fields you want to read in fetch_all. Also, reading into existing properties is faster.
Disadvantage: if you want to read different sets of information into the same class, this is not possible. The person in my example below always has an id, surname and gender. If you want to read for instance id only, you have to introduce another class. But would you want that?..
class Entity {
function __set($prop, $value) {
// Ignore any property that is not declared in the descendant class.
}
}
class Person extends Entity {
public $id = null;
public $surname = null;
public $gender = null;
}
$stmt->fetchAll(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'Person')
Solution 3: Read only the fields you want
This is actually the best solution. Instead of selecting all fields, SELECT *, select only the fields you want to have: SELECT id, surname, gender ... This way, you won't have objects with too many values, but more importantly, you also decrease the load on your database. The database doesn't need to fetch the data, PHP doesn't need to receive it, and if the database server is separate from the webserver, you also save network traffic. So in all regards, I think this is the best option.
I use Redbeanphp ( http://redbeanphp.com/ ) in my php project. And i want to use a table prefix for my tables.
Redbeanphp can't support table prefix since the version 3.0. But i want to extend Redbeanphp to support table prefix in my project.
I don't want to modify the redbeanphp code. But if there's no solution, i'll do that.
I have already tried to replace the QueryWriter of Redbeanphp but the QueryWriter class is not always the same (it depends of the type of my database).
What is the best way to do that ?
I now got the response so i answer to myself.
Once redbean is initialized, you can configure a new toolbox. The toolbox in redbean handle 3 important objects : The query writer, the Redbean OODB and the database adapter. You can access the current redbean toolbox with R::$toolbox
You can do this code :
R::configureFacadeWithToolbox(new RedBean_ToolBox(R::$redbean, R::$adapter, R::$writer));
This code does nothing. Because you configure Redbean with a new toolbox but with the same OODB, the same database adapter and the same query writer. But in this code, you can replace one of these object by your own object.
Example, replacing the writer by a dummy writer :
$writer = new MyQueryWriter();
R::configureFacadeWithToolbox(new RedBean_ToolBox(R::$redbean, R::$adapter, $writer));
The probem is the following :
You want to replace the query writer by your own query writer to handle a table prefix
The query writer class is not always the same. Redbean use 5 classes for the query writer. The class depends of the database type. For instance, if you use a Mysql database, the query writer class is RedBean_QueryWriter_MySQL
You don't want to write an entire query writer.
Redbean query writer possible classes are :
RedBean_QueryWriter_CUBRID
RedBean_QueryWriter_MySQL
RedBean_QueryWriter_Oracle
RedBean_QueryWriter_PostgreSQL
RedBean_QueryWriter_SQLiteT
So, this is my solution. I wrote 5 littles classes.
class MyCubridQueryWriter extends RedBean_QueryWriter_CUBRID {
public function safeTable($name, $noQuotes = false) {
$name = prefix($name);
return parent::safeTable($name, $noQuotes);
}
}
class MyMysqlQueryWriter extends RedBean_QueryWriter_MySQL {
public function safeTable($name, $noQuotes = false) {
$name = prefix($name)
return parent::safeTable($name, $noQuotes);
}
}
class MyOracleQueryWriter extends RedBean_QueryWriter_Oracle {
public function safeTable($name, $noQuotes = false) {
$name = prefix($name)
return parent::safeTable($name, $noQuotes);
}
}
class MyPostgreSqlQueryWriter extends RedBean_QueryWriter_PostgreSQL {
public function safeTable($name, $noQuotes = false) {
$name = prefix($name)
return parent::safeTable($name, $noQuotes);
}
}
class MySQLiteTQueryWriter extends RedBean_QueryWriter_SQLiteT {
public function safeTable($name, $noQuotes = false) {
$name = prefix($name)
return parent::safeTable($name, $noQuotes);
}
}
As you can see, each class extend a Redbean query writer class. We override the safeTable method. Redbean always use safeTable on a table name. The prefix function is simple :
function prefix($table) {
return "my_prefix_$table";
}
So now, in our code. We can use an array to map a Redbean query writer class to our own classes and replace it. Here we are :
$writerMapping = array(
'RedBean_QueryWriter_CUBRID' => 'MyCubridQueryWriter',
'RedBean_QueryWriter_MySQL' => 'MyMysqlQueryWriter',
'RedBean_QueryWriter_Oracle' => 'MyOracleQueryWriter',
'RedBean_QueryWriter_PostgreSQL' => 'MyPostgreSqlQueryWriter',
'RedBean_QueryWriter_SQLiteT' => 'MySQLiteTQueryWriter'
);
$class = $writerMapping[get_class(R::$writer)];
$writer = new $class(R::$adapter);
R::configureFacadeWithToolbox(new RedBean_ToolBox(R::$redbean, R::$adapter, $writer));
Et voila. Now Redbean will use your own writer and you can do what you want ! With our safeTable method, we add a prefix to every table name in the database.
I ran into this problem when wanting to use RedBean with Wordpress. My solution was to create another class (WPR for "wordpress redbean"), like so:
class WPR {
public static function __callStatic($method, $params)
{
global $wpdb;
$prefix = $wpdb->base_prefix;
foreach ($params as &$param)
$param = preg_replace('/\{([a-zA-Z0-9_]+)\}/', $prefix . '$1', $param);
// switch to wordpress database
R::selectDatabase('WPR');
// do the dang thing
$res = call_user_func_array(array('R',$method),$params);
// go back
R::selectDatabase('default');
// send it
return $res;
}
};
R::addDatabase('WPR', "mysql:host=".DB_HOST.";dbname=".DB_NAME, DB_USER, DB_PASSWORD);
I also wanted this class to use a different database than my 'regular' redbean class, so I have the selectDatabase() calls in there. Comment them out if you don't need them.
What it does is it acts as a proxy to redbean, but with each input it checks for some substring like {this} and it expands it out into the full database name, with prefix. Here's an example of your usage:
$my_blog = WPR::find('{blogs}', 'domain=?', array('mydomain.com')); or
$allowed_hosts = WPR::getCol('SELECT domain FROM {blogs}');
In those two cases, {blogs} gets converted to wp_blogs
Magus,
I have the same problem as you. I tried your solution but could not get it working. I wrote a couple of functions for prefixing and my object names into table names and back, which I think will work in my case, but I'd still like to get your way working since it'll be more transparent. I have unprefixed table names working for reading and writing.
I noticed was that Oracle support isn't available out-of-the-box in RedBean, so I added checks for each classname to avoid errors:
if (class_exists('RedBean_QueryWriter_MySQL', false)) {
class MyMysqlQueryWriter extends RedBean_QueryWriter_MySQL {
...
}
The checks should work, I got output to my log within my MySQL (which I'm using) block while loading the prefixing code.
Also, at the end there you wrote:
$class = $writerMapping(get_class(R::$writer));
but you probably meant:
$class = $writerMapping[get_class(R::$writer)];
Based on some debugging, my R::$writer has been changed after configureFacadeWithToolbox, but, for some reason the table names aren't being converted, and nothing within my custom safeTable function is being executed.
If you could give any more info on how you tested your method or what I could be missing, I'd be glad to hear it.
(I'm sorry this message isn't an answer to your question, but I really couldn't find any other way to send you a message or comment on your answer. Damn Stack Overflow! (Just kidding, I love it.))
I'm not quite sure how to correctly put this question. I want to dynamically call functions that are contained in classes (I think this means they are called 'methods').
Here is an example of my code which I hope helps explain what I am trying to achieve.
In this instance $result returns all the different modules that are loaded. This then checks if the module's PHP file has been included with it's class, then if that class exists - trys to call the class directly.
foreach ($results as $result) {
$moduleclass_name = 'TestClassName_' . $result->module_name . '::FunctionToCall';
if (method_exists($moduleclass_name, 'FunctionToCall'))
$VariableToRetrieve = $modulefunction_name($Parameter1, $Parameter2);
}
This returns an error
"Call to undefined function
TestClassName_modulename::FunctionToCall()"
although the 'TestClassName' has been declared correctly.
Can someone tell me what I'm doing wrong?
What you want is probably call_user_func_array().
The code would look similar to this:
call_user_func_array(array($classNameOrInstance, $functionName), array($arg1, $arg2, $arg3));
EDIT Also, in your example you seem to have included the function name in the class parameter for method_exists, too...
You may use call_user_func() as such to achieve what you are trying to do. Also, it is better to use is_callable() instead of method_exists() to validate if the method is callable (method may exist but its visibility may prevent it from being callable.
foreach ($results as $result) {
$module_callback = array('TestClassName_' . $result->module_name,'FunctionToCall');
if (is_callable($module_callback))
$VariableToRetrieve = call_user_func($module_callback, $Parameter1, $Parameter2);
}
I think it doesn't work because your syntax may not support "static method calls".
I suggest you give a try to Franz's method call_user_func().
I did a similar thing on a former project.
It was designed to call a class which implemented an interface, so method names where known.
I don't think it's difficult to modify this code in order to make it match with yours.
class CDispatcher {
public static function GetDispatcher( $module = 'core' ) {
$class_name = $module . 'Dispatcher';
try {
// looks for the file associated with the class
// if the file is not found an exception is raised
search_class( $class_name );
} catch ( exception $e ){
throw new UnkwownModuleException($module);
}
return new $class_name();
}
}
// Then, you call this class :
$new_instance = CDispatcher::GetDispatcher( $my_module );