11

Let's say I have 2 php objects:

<?php
class Post {
    public $id;
    public $text;
    public $user_id;
}
?>

and

<?php
class User {
    public $id
    public $name
}
?>

Every post has a unique constraint with 1 user in the database.

I want to fill data into the "Post"-object with PDOs "FETCH_CLASS" method which works for all the "Post" attributes but how do I fill the attributes in "User"?

My SQL-statement looks like this:

SELECT post.id, 
       post.text, 
       post.user_id, 
       user.id, 
       user.name 
FROM POST INNER JOIN User on post.user_id = user.id

Thanks!

UPDATE:

ATM I fill my "Post"-class like this:

    $statement = $db -> prepare($query);
    $statement -> execute();
    $statement -> setFetchMode(PDO::FETCH_CLASS, 'Post');
    $posts = $statement -> fetchAll();

So how would I have to change that for also filling the other class "User"?

SOLUTION:

$statement = $db -> prepare($query);
$statement -> execute();
$posts = array();
while (($row = $statement->fetch(PDO::FETCH_ASSOC)) !== false) {
    $post           = new Post();
    $post->id       = $row['post_id'];
    $post->text     = $row['post_text'];
    $post->created  = $row['post_created'];
    $post->image    = $row['post_image'];
    $post->url      = $row['post_url'];
    $post->weight   = $row['post_weight'];
    $post->likes    = $row['post_likes'];
    $user           = new User();
    $user->id       = $row['user_id'];
    $user->nickname = $row['user_nickname'];
    $user->created= $row['user_created'];
    $user->locked   = $row['user_locked'];
    $post->user     = $user;
    $posts[] = $post;
}
return $posts;
Raphael Jeger
  • 5,024
  • 13
  • 48
  • 79

5 Answers5

3

You can try using __set method like this:

<?php

include 'connection.php';

class Post {

    public $id;
    public $text;
    public $user;

    public function __construct() {
        $this->user = new User();
    }

    public function __set($name, $value) {

        if (array_key_exists($name, get_object_vars($this->user))) {
            $this->user->$name = $value;
        } else {
            $this->$name = $value;
        }
    }

}

class User {

    public $id;
    public $name;

}

$statement = $pdo->prepare("SELECT * FROM post "
        . "LEFT JOIN user "
        . "ON post.user_id = post.id");
$statement->execute();

$result = $statement->fetchAll(\PDO::FETCH_CLASS | \PDO::FETCH_PROPS_LATE, Post::class);
echo "<pre>";
var_dump($result);
celsowm
  • 846
  • 9
  • 34
  • 59
  • Doesn't that mean that `Post::$id` will always be `null`? – bancer Jul 15 '19 at 07:45
  • This is an interesting solution but not the most efficient way of doing it. You could do `if (isset($this->$name)) $this->$name = $value; else $this->user->$name = $value`. (There's even a shorter way I think...) – mojuba Jan 11 '22 at 18:10
2

Theres no support for the directly in PDO as far as I'm aware. Typically if you need to create a complex object graph from the result of query thats the responsibility of an ORM.

If you need this functionality i wold recommend using Doctrine or Propel as opposed to writing something yourself. There are others too that may be lighter weight, but i have no experience with them.

EDIT:

I think maybe i misunderstood the question as im sure others might. I think the real question was how to get access to the joined columns, not cessarially how to create an object from them.

In that case simply using a standard arry fethc method like PDO::FETCH_ASSOC, PDO::FETCH_NUMERIC or PDO::FETCH_BOTH will give you all the columns you queried.

So if you want to turn that into an "object graph" you have to do it manually not by using PDO::FETCH_CLASS.

For example:

//$db is pdo:
// also notice im aliase the columns prefixing the name so that we can tell what belongs to
// post and what belongs to user, an alternative approach would be to use FETCH_NUMERIC,
// which just uses the column positions from the seelct statement as the keys
// so in this case post.id would be in the array as key 0, and user.name would be in the
// array as key 4
$stmt = $db->prepare('SELECT post.id as p_id, 
       post.text as p_text, 
       post.user_id as p_user_id, 
       user.id as u_id, 
       user.name as u_name
FROM POST INNER JOIN User on post.user_id = user.id');

$stmt->execute();

while (($row = $stmt->fetch(PDO::FETCH_ASSOC)) !== false) {
   print_r($row);
   /* will output:
      Array (
         'p_id' => 'value'
         'p_text' => 'value'
         'p_user_id' => 'value'
         'u_id' => 'value',
         'u_name' => 'value'
      )
   So now you need to decide how to create your objects with the information returned
   */
}
prodigitalson
  • 60,050
  • 10
  • 100
  • 114
  • thanks. ATM I think I should have stayed away from PDO if this is too "complex". Which applications do even exist without a simple join? – Raphael Jeger Mar 04 '13 at 13:44
  • I dont understand whats so complex about it especially considering the only other option is `mysqli` which is harder to work with and more verbose. Neither support "hydrating" a complex relationship from a joined query result. – prodigitalson Mar 04 '13 at 13:50
  • I'm not sure if I understand your comment correctly. Do you agree that PDO should support that? Does it support it when not using FETCH_CLASS? I'll look into that now... – Raphael Jeger Mar 04 '13 at 13:54
  • If you used `FETCH_ASSOC` or `FETCH_NUMERIC` it will give you all columns from the query... I thought you were asking specifically how to create multiple objects from a JOINED query result. – prodigitalson Mar 04 '13 at 13:56
  • I'm not intentionally unclear but I understand it not enough to be more specific ;) I only try to read with my "JOINED" query and fill both objects, can you help me how to get there? – Raphael Jeger Mar 04 '13 at 13:57
  • finally did it thanks to you. I'll append the solution above. THANKS! – Raphael Jeger Mar 04 '13 at 15:03
  • @Raphael Jeger You could add a "post_author" field/attribute to your PHP post class. And then just join the required tables together. – Jo Smo Sep 18 '14 at 07:18
2

Not really a response for the OQ, but because it keeps popping on Google (yes I know its over a year old). You'll find that it is AMAZINGLY faster to just skip loops and query each table separately.


    SELECT post.id, 
           post.text, 
           post.user_id, 
    FROM POST INNER JOIN User on post.user_id = user.id
      $statement = $db -> prepare($query);
        $statement -> execute();
        $statement -> setFetchMode(PDO::FETCH_CLASS, 'Post');
        $posts = $statement -> fetchAll();

    SELECT user.id, 
           user.name 
    FROM POST INNER JOIN User on post.user_id = user.id
      $statement = $db -> prepare($query);
        $statement -> execute();
        $statement -> setFetchMode(PDO::FETCH_CLASS, 'User');
        $users = $statement -> fetchAll();
    
Cbunny
  • 29
  • 2
1

Maybe use PDO::FETCH_NAMED if you work multiple tables. Or use PDO::ATTR_FETCH_TABLE_NAMES.

hovszabolcs
  • 101
  • 1
  • 2
0

My approach for solution:

function groupQueryJoinClasses(\PDOStatement $stmt, $joinInfo = [], $idProperty = 'id')
{
    $result = [];

    $records = $stmt->fetchAll();

    if ( !empty($joinInfo) ) {
        foreach ($records as $record) {
            if ( !isset($result[$record->$idProperty]) ) {
                $result[$record->$idProperty] = $record;
            }
            foreach ($joinInfo as $target => $classInfo) {
                $vars = get_object_vars($record);
                $class = new $classInfo['class']();
                foreach ($vars as $key => $value) {
                    $keyData = explode('.', $key);
                    if ( $keyData[0] == $classInfo['prefix']) {
                        $class->$keyData[1] = $value;
                        unset($result[$record->$idProperty]->$key);
                    }
                }
                if ( !is_array( $result[$record->$idProperty]->$target) ) {
                    $result[$record->$idProperty]->$target = [];
                }
                $targetArray = &$result[$record->$idProperty]->$target;
                $targetArray[] = $class;
            }
        }
    } else {
        $result = $records;
    }

    return $result;
}

function getModel($query, $data, $entryClass, $joinInfo, $idProperty = 'id') {
    $pdo = new PDO(...);
    $stmt = $pdo->prepare($query);
    $stmt->execute($data);

    $stmt->setFetchMode(\PDO::FETCH_CLASS, $entryClass);

    return groupQueryJoinClasses($stmt, $joinInfo , $idProperty);
}

// Sample request

$query =
    'SELECT
        u.id as "id",
        p.id as "Post.id",
        p.name as "Post.name"
    FROM `user` u
    LEFT JOIN `posts` p ON p.user_id = u.id
    where id = :id'
;
$data = [ ':id' => 1 ];

$joinInfo = [
    'posts' => [
        'class' => Post::class,
        'prefix'=> 'Post'
    ]
];
$flowRules = getModel($query, $data, User::class, $joinInfo);

Maybe interesting for anyone, or maybe someone would see issue in such approach

Pavel Budo
  • 23
  • 1
  • 6