j4mie / idiorm

A lightweight nearly-zero-configuration object-relational mapper and fluent query builder for PHP5.
http://j4mie.github.com/idiormandparis/
2.01k stars 369 forks source link

find_many to json returns empty data #230

Closed ghost closed 10 years ago

ghost commented 10 years ago

$users = ORM::for_table('users')->find_many();
$data = json_encode($users); // Returns empty data array

I know that I can use find_array function, but I need to manipulate objects in array before convert to json format.

I found another way to reach the goal:


$users = ORM::for_table('users')->find_result_set();
$users_array = [];
foreach ($users as $u) {
    // Do some work with $u
    $users_array[] = $u->as_array();
}
$data = json_encode($users_array); // Return full data array

But it's bad in performance. I need find_many function, but it must return not empty data array when convert to json.

treffynnon commented 10 years ago

I don't see any manipulation ocurring here. Please can you provide actual code so I can see your intentions more clearly.

I don't see why you can't use find_array here with the current samples you've provided. array_filter or array_map combined with find_array should cover all the situations a result_set or an array from find_many could anyway.

ghost commented 10 years ago

I agree with you @treffynnon. And I used find_array() function in my code till I start using idiorm/paris. Let's see some code:


class User extends Model {
    public function tweets() {
        return $this->has_many('Tweet');
    }
}
class Tweet extends Model {}
$users = Model::factory('User')->find_many();
foreach ($users as $user) {
    $user->tweets = $user->tweets()->find_many();
}

And you can clearly see that if I use find_array() function then I can't use tweets() method.


Model::factory('User')->find_array();
foreach ($users as $user) {
    $user['tweets'] = $user->tweets()->find_array(); // Invalid - use tweets() method of non-object
}

treffynnon commented 10 years ago

Yep, so that is why I was asking. Unfortunately Idiorm is constrained by the same universal laws as the rest of the world. Convenience comes with a cost - if you want to use the quick methods like that then you're going to have to pay the performance price.

When you query like that in a loop you're actually generating a query for each iteration of the loop so it must hit the database each time. Queue slow down.

This is where the 80/20 rule from Idiorm's philosophy really kicks in - joins, which look like a fit for speeding up these queries quickly are not automatically generated so you'll have to build them yourself with the query builder.

I would recommend a static method on your model that you specify this query in yourself.

ghost commented 10 years ago

Yeap, I understand you. But it's true when use "one-to-one" relationship, then it's better way to use "join" feature, I agree with you. And what if I need to get result in case of "one-to-many" relationship? Anyway I make a query in a loop to reach the goal, and it would be better if I'll be able to use model objects and then convert them to json format. I'm interested in your opinion.

charsleysa commented 10 years ago

Try this:

class User extends Model {
    public function tweets() {
        return $this->has_many('Tweet');
    }
}

class Tweet extends Model {}

$jsonUsers = array();
foreach (Model::factory('User')->find_result_set() as $user) {
    $u = $user->as_array();
    $u['tweets'] = $user->tweets()->find_array();
    $jsonUsers[] = $u;
}

The problem is you have to sacrifice performance somewhere, so you have to choose whether you want to sacrifice DB performance by having lots of queries or do you want to sacrifice PHP performance by having sifting loops?

tag commented 10 years ago

@redjee, So far as converting to Json, that's easily accomplished, as I'm sure you're aware. In the hopes that perhaps it might help you or others, I've included a reduced form of how I deal with JSON using Paris, below. I've done a little extra work, as I sometimes want a JSON object with all of the relationships included, and sometimes I don't.

trait JsonModel
{
    /**
     * @param $full bool Whether relationships should be fetched and added to the eventual JSON object;
     */
    public function toJson($full = false)
    {
        return json_encode($this->jsonSerialize(), DEBUG ? JSON_PRETTY_PRINT : null);
    }

    /**
    *  This method fulfills the signature for JsonSerializeable interface.
     * @param $full bool Whether relationships should be fetched and added;
     */
    public function jsonSerialize($full = false)
    {
        // ...
        // I choose to always add 'model' and 'canonical' (url) to json data.
       // I've removed that code, but simply add those as lines in array_merge parameter
        return array_merge(
            $this->asArray(),
            $full ? $this->jsonSerializeMore() : []
        );
    }

    /**
     * Override when necessary, if you want to pass relations in the same object.
     */
    protected function jsonSerializeMore() {
        return [];
    }
} // end trait

class Foo extends \Model implements \JsonSerializable
{
    use  \Phibula\Models\Traits\JsonModel;  // Adjust namespace, of course
}

http://php.net/manual/en/jsonserializable.jsonserialize.php