Where to put and how to load result object class in Codeigniter? - php

I'm looking for proper way to organize 'Result object classes' in CodeIgniter. These classes are usualy used in models as described in documentation:
You can also pass a string to result() which represents a class to
instantiate for each result object (note: this class must be loaded)
$query = $this->db->query("SELECT * FROM users;");
foreach ($query->result('User') as $row)
{
echo $row->name; // call attributes
echo $row->reverse_name(); // or methods defined on the 'User' class
}
So, where to put 'User' class and is there any 'offical' way how to load it?

Try using a library.
Place your class in the application/libraries folder, then use the loader to load it:
$this->load->library('user');

Not exactly what you are asking for, but I often use a MY_Model with:
public function merge_object(&$object, $data){
if (is_object($data)) {
$data = get_object_vars($data);
}
if (!empty($data)) {
foreach ($data as $property => $value) {
if (!property_exists($object, $property)) {
$object->$property = $value;
}
}
}
return $object;
}
and then call $this->merge_object($this, $data); from the __construct($data = array())
When you call a model function now, it returns a an instance of the model class as well, and so you can access any of its methods. It does essentially what you are trying to do.

Related

PHP modify return

Hello i try do next logic
i have two class
abstract class Repository {
}
class HomeRepository extends Reposytory {
public function getMyAwesome() {
return array()
}
}
i dont wish get array, i wish get object, but i wish do some little magic,
i create hydrator class, his create object from array
now this seems like
public function getMyAwesome()
{
$awesomes = $this->adapter->table('awesome')->get();
$result = [];
foreach ($awesomes as $aw) {
$result[] = $this->hydrator->hydrate($aw);
}
return $result;
}
but i dont wish do this in child class, i wish enter this to parent, or gate
another word i wish see in my child class next code
public function getMyAwesome()
{
return $this->adapter->table('awesome')->get(); //MUST BE HYDRATED
}
How i can do that?
Okay, answer is very simple. Must use Proxy Class

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();

Is there a way to set only defined properties when using fetch_object on a mysqli_result?

If I have a class:
class ExampleClass {
private $thing1;
private $thing2;
}
I can use fetch_object('ExampleClass') on a mysqli_result object to create an instance of ExampleClass. Using this method, the private properties of ExampleClass will be set, provided the query has columns with the same names.
In addition to those properties, other public properties will be added for any other columns in the query. Is there any way to avoid this? I couldn't find anything about it in the php documentation for fetch_object.
If I set up ExampleClass with a constructor like this
function __construct($properties = []) {
foreach ($properties as $key => $value)
if (property_exists($this, $key))
$this->$key = $value;
}
I can get the row from the result set using fetch_assoc instead of fetch_object, and then create a new ExampleClass with the resulting array as an argument. This achieves what I am going for, but I was hoping for something more direct.
For one of my projects I have built a system just like that.
All classes are derived of the basic abstract Object class which, among others, offers a cloneInstance() method. Then, in the concrete implementing class, I'm simply going to use it ('I'm assuming that $pdo is somehow accessible here, for brevity):
Note that cloneInstance() uses reflection to check if the target instance actually has a euqlly named property ($drfl->hasProperty()).
abstract class Object {
protected function cloneInstance($obj) {
if (is_object($obj)) {
$srfl = new ReflectionObject($obj);
$drfl = new ReflectionObject($this);
$sprops = $srfl->getProperties();
foreach ($sprops as $sprop) {
$sprop->setAccessible(true);
$name = $sprop->getName();
if ($drfl->hasProperty($name)) {
$value = $sprop->getValue($obj);
$propDest = $drfl->getProperty($name);
$propDest->setAccessible(true);
$propDest->setValue($this,$value);
}
}
}
return $this;
}
class MyAutomaticClass extends Object {
// static loader
public static function load($id) {
$result = null;
$sql = 'SELECT * FROM mytable WHERE id=:id';
$stmt = $pdo->prepare($sql, array(':id' => $id));
$list = $stmt->fetchAll(PDO::FETCH_OBJ);
if (count($list)) {
$result = new MyAutomaticClass($list[0]);
}
return $result;
}
// constructor makes use of base Objects cloning feature
public function __construct($obj=null) {
if (is_object($obj)) {
$this->cloneInstance($obj);
}
}
}

Write a Model Class in CodeIgniter with ORM style

I thought about using an actual ORM like Doctrine, then I figured its download link was even broken...and all tutorials online dated back to 2011. Also I'll have to write yaml files.
Then I start off by trying to write my own model class in ORM style.
I just fill it up with fields and save it to database, which is easy.
But I encounter a problem trying to retrieve data from database.
class User extends CI_Model {
public $id;
public $email;
public $displayName;
public function save() {
.....
}
public function search_by_email($email) {
$user = new User();
$this->db->select('email')->from('user')->where('email', $email);
$result = $this->db->get();
if ($result->num_rows()==0) {
return false;
}else {
foreach ($result->result() as $field) {
}
}
}
I know normally in CodeIgniter, you return $query->result(), well as ORM custom, I'm trying to return an object...Is there a way to do this? What function should I use?
result takes a string that represents a class that it will instantiate and assigned the result data (each field as a property of the object):
$query = $this->db->query("SELECT * FROM users;");
foreach ($query->result('User') as $row)
{
echo $row->name; // call attributes
echo $row->reverse_name(); // or methods defined on the 'User' class
}
regarding your comment, i'm pretty sure that codeigniter has no idea about anything regarding the class you pass to the result method. It looks like it just instantiates it and sets property and valuefor each column/value returned from the db:
$this->custom_result_object[$class_name][$i] = new $class_name();
foreach ($this->{$_data}[$i] as $key => $value)
{
$this->custom_result_object[$class_name][$i]->$key = $value;
}
One ORM that works quite well with codeIgniter is php-activerecord which is based off the rails active record model.
The function your trying to copy "search_by_email" is done through a
Late static binding method.
So you might see functions that are called like so:
Object::find_by_email()
Object::search_by_email()

I need to collect an array of classes to call their static variables in php

First thing i want to say that it's not an easy question to explain, so please be patient if it seems confusing.
I have a set of classes like this
class Product {
public static $static_type = 'product';
public static $static_table = 'product_table';
public function __construct($params) { //do some }
}
and then there are the classes News, Events etc
From another class i need to access to those static variables inside these classes in an iterative way. Something like:
//...
if (Product::$static_type) { //do some }
else if (News::$static_type) { //do other }
//...
I want to trasform it in a cycle, like foreach in a way like this (it's not correct but makes sense to my question)
foreach ($classes as $class) {
echo $class::$static_type; //brrrr, just to render the idea :)
}
So i think about a singleton/static class that has a static method returning an array of my classes (not instantiated). Like this:
class Conf {
public function __construct() {
//nothing
}
public static function get_class_array () {
//how to do this???
}
}
and then
foreach (Conf::get_class_array() as $class) {
echo $class::$static_type; //brrrr, just to render the idea :)
}
How i can reach this? I don't want to instantiate Product, News or others in this case.
Edit: eval is evil, i don't want to use it. No tricks with get_declared_class, if there's no way to solve I will use reflection, that i think it's the more elegant way among the mentioned :(.
Edit: in the meantime i'll do the Conf::get_class_array() in this way
public static function get_class_array () {
return array(new ReflectionClass('Prodotto'), new ReflectionClass('News'));
}
and then call it here:
foreach (Conf::get_class_array() as $class) {
echo $class->getStaticPropertyValue('static_type');
}
I don't think you can do this. You could however do one of these:
$properties = get_class_vars('Product');
echo $properties['static_type'];
or
$class = new ReflectionClass('product');
echo $class->getStaticPropertyValue('static_type');
Note that in PHP 5.3 echo $class::$static_type; will work (http://php.net/manual/en/language.oop5.static.php)
Until 5.3.0, you can try this method. Create a container class as you suggested (we'll call it Conf to stick with what you had), and provide two methods for setting and getting applicable classes that you want to iterate over:
<?php
class Conf {
private static $instance;
private $classes = array();
public static function getInstance() {
if ( is_null(self::$instance) ) {
self::$instance = new self();
}
return self::$instance;
}
public function registerClass($className) {
// Use associative index to maintain uniqueness
$this->classes[$className] = $className;
}
public function getRegisteredClasses() {
return $this->classes;
}
}
Some example classes and how to register them:
class X {
public static $a = "catus";
public static $b = "pants";
}
class Y {
public static $a = "apples";
public static $b = "bananers";
}
$conf = Conf::getInstance();
$conf->registerClass("X");
$conf->registerClass("Y");
Now, to access and/or alter the static members, you can do something like the following (using RefelectionClass as tom Haigh pointed out):
$conf = Conf::getInstance();
echo "<pre>";
foreach ( $conf->getRegisteredClasses() as $class ) {
$reflection = new ReflectionClass($class);
echo "<hr/>Class: $class\n";
// Access example
print_r( $reflection->getStaticProperties() );
// Alter example
$reflection->setStaticPropertyValue("a",
$reflection->getStaticPropertyValue("a") . "-modified"
);
print_r( $reflection->getStaticProperties() );
}
If you have a class naming convention like Com_Example_Static_X and Com_Example_Static_Y, you can simplify Conf->getRegisteredClasses() (and even make it a static method if you so desire) by doing as n3rd suggested:
class Conf {
// ....
static public function getMatchingClasses($pattern="/^Com_Example_Static_.+$/") {
$response = array();
foreach ( get_declared_classes() as $className ) {
if ( preg_match($pattern, $className, $m) ) {
$response[] = $className;
}
}
return $response;
}
}
And, of course, update your foreach to:
foreach ( Conf::getMatchingClasses() as $class ) {
// ...
}
Hope that was helpful.
You can use get_declared_classes() to get a list of classes. This will be all class though, not just the ones you've declared.
You should make all your classes inherit from a base class:
class Product extends MyBase {}
Then you can list the classes like this
function get_class_array()
{
$myClasses = array();
foreach (get_declared_classes as $class)
{
if (is_subclass_of($class, 'MyBase'))
$myClasses[] = $class;
}
return $myClasses;
}
Then you can get the data like this:
foreach (get_class_array() as $class)
echo eval("return $class::\$foo;"); // Yes yes, eval is evil, we know...
To get a list of classes, you can use get_declared_classes. Then you'll have to determine which of those classes you want to process.
You could do this by looking for a common base class with is_subclass_of, or using ReflectionClass to see if it has the static member variables you are interested in.
I don't think there's an easy way to do this. Here are a few ideas off the top of my head how you could go about doing this:
Use get_declared_classes() to retrieve a list of all defined classes and check them against your naming scheme (e.g. MyNamespace_*) or whether they implement an interface (e.g. MyStaticEnumerable).
Kinda like the above, but a little more sophisticated: write your on class loader and have it check whether a loaded class is one of ones you want to enumerate. If so, make it known to some manager class.
Check the directory in which the classes are defined to manually enumerate all classes.

Categories