user544452
user544452

Reputation:

Doctrine ODM (MongoDB) - Get a complete array of an object?

iv'e got a problem to receive a complete array (with all the data of the embedded childs collections and objects) of my document. My document looks exactly like this one:

use Doctrine\Common\Collections\ArrayCollection;

/** @Document(collection="user") */
class User {

/** @Id */
protected $id;

/** @String */
protected $firstname;

/** @String */
protected $lastname;

/** @EmbedMany(targetDocument="Email") */
protected $email;

/** @EmbedMany(targetDocument="Address") */
protected $address;

/** @EmbedMany(targetDocument="Subscription") */
protected $subscription;


/**
* Construct the user
*
* @param   array $properties
* @throws  User_Exception
*/
public function __construct(array $properties = array()) {

    $this->email = new ArrayCollection();
    $this->address = new ArrayCollection();
    $this->subscription = new ArrayCollection();

    foreach($properties as $name => $value){
        $this->{$name} = $value;
    }

}


...

I need a complete array of an embedded collection to output the whole data and render it by json. My query looks like this:

$query = $this->_dbContainer->getDocumentManager()->createQueryBuilder('User')->field('deletedAt')->exists(false);                          
$result = $query->field('id')->equals($id)->getQuery()->getSingleResult();

For example, if i call the toArray() function like this:

$array = $result->getSubscription()->toArray();
print_r($array);

Then the output ist just an array on top level:

[0] => Object Subscription...
[1] => Object Subscription...
...

How can i easily get an array like this?

[0] => array('subscriptionLabel' => 'value1', 'field' => 'value1', ...)
[1] => array('subscriptionLabel' => 'value2', 'field' => 'value2', ...)
...

Are there any best practises or maybe some missing helper scripts to prevent something ugly like this code (how to handle child -> child -> child szenarios? ugly -> ugly ugly -> ugly ugly ugly -> ...):

$example = array();
foreach($result->getSubscription() as $key => $subscription) {
    $example[$key]['subscriptionLabel'] = $subscription->getSubscriptionLabel();
    $example[$key]['field'] = $subscription->getField();
    ...
}

Thanks a lot, Stephan

Upvotes: 5

Views: 12094

Answers (4)

OskHas
OskHas

Reputation: 11

Just made this a bit more generic, works perfect. Just dont forget to extend it with your documents and embeds.

<?php
namespace App\Documents;

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Doctrine\ODM\MongoDB\PersistentCollection;

/**
 * @ODM\MappedSuperclass
 */
abstract class BaseDocument implements \JsonSerializable
{
    /**
     * @return array
     */
    public function jsonSerialize()
    {
        return $this->toArray();
    }

    /**
     * @return array
     */
    public function toArray()
    {
        $getterNames = get_class_methods(get_class($this));
        $gettableAttributes = [];
        foreach ($getterNames as $funcName) {
            if (substr($funcName, 0, 3) !== 'get') {
                continue;
            }

            $propName = strtolower(substr($funcName, 3, 1));
            $propName .= substr($funcName, 4);
            $value = $this->$funcName();

            $gettableAttributes[$propName] = $value;


            if (is_object($value)) {
                if ($value instanceof PersistentCollection) {
                    $values = [];
                    $collection = $value;
                    foreach ($collection as $obj) {
                        /** @var BaseDocument $obj */
                        if ($obj instanceof \JsonSerializable) {
                            $values[] = $obj->toArray();
                        } else {
                            $values[] = $obj;
                        }
                    }
                    $gettableAttributes[$propName] = $values;
                } elseif ($value instanceof \JsonSerializable) {
                    /** @var BaseDocument $value */
                    $gettableAttributes[$propName] = $value->toArray();
                }
            }
        }

        return $gettableAttributes;
    }
}

Upvotes: 0

Cedric
Cedric

Reputation: 545

Tanks to Rooster242, you can even recursively apply toArray to embedded documents which themself extends BaseDocument by using the php is_subclass_of function :

/**
 * @ODM\MappedSuperclass
 */
abstract class BaseDocument implements \JsonSerializable 
{
    public function jsonSerialize() {
        return $this->toArray();
    }

    public function toArray() {
        $getter_names = get_class_methods(get_class($this));
        $gettable_attributes = array();
        foreach ($getter_names as $key => $funcName) {
            if(substr($funcName, 0, 3) === 'get') {
                $propName = strtolower(substr($funcName, 3, 1));
                $propName .= substr($funcName, 4);
                $value = $this->$funcName();
                if (is_object($value) && is_subclass_of($value,"BaseDocument")) {
                    $gettable_attributes[$propName] = $value->toArray();
                } elseif (is_object($value) && get_class($value) == 'Doctrine\ODM\MongoDB\PersistentCollection') {
                    $values = array();
                    $collection = $value;
                    foreach ($collection as $obj) {
                        if (is_subclass_of($obj,"BaseDocument")) {
                            $values[] = $obj->toArray();
                        } else {
                            $values[] = $obj;
                        }
                    }
                    $gettable_attributes[$propName] = $values;
                }
                else {
                    $gettable_attributes[$propName] = $value;
                }
            }
        }
        return $gettable_attributes;
    }
}

Upvotes: 1

Rooster242
Rooster242

Reputation: 920

I ran into this same need recently and solved it by creating a base class for all my entities with a toArray() function and JsonSerializable. It converts all nested references as well.

/**
 * @ODM\MappedSuperclass
 */
abstract class BaseDocument implements \JsonSerializable 
{
    public function jsonSerialize() {
        return $this->toArray();
    }

    public function toArray() {
        $getter_names = get_class_methods(get_class($this));
        $gettable_attributes = array();
        foreach ($getter_names as $key => $funcName) {
            if(substr($funcName, 0, 3) === 'get') {
                $propName = strtolower(substr($funcName, 3, 1));
                $propName .= substr($funcName, 4);
                $value = $this->$funcName();
                if (is_object($value) && get_class($value) == 'Doctrine\ODM\MongoDB\PersistentCollection') {
                    $values = array();
                    $collection = $value;
                    foreach ($collection as $obj) {
                        $values[] = $obj->toArray();
                    }
                    $gettable_attributes[$propName] = $values;
                }
                else {
                    $gettable_attributes[$propName] = $value;
                }
            }
        }
        return $gettable_attributes;
    }
}

Now I can serialize the entity as an array or json string with json_encode($doc). Bam.

Upvotes: 1

user544452
user544452

Reputation:

Damn simple answer! Just use ->hydrate(false) and it's done.

For find queries the results by default are hydrated and you get document objects back instead of arrays. You can disable this and get the raw results directly back from mongo by using the hydrate(false) method:

<?php

$users = $dm->createQueryBuilder('User')
    ->hydrate(false)
    ->getQuery()
    ->execute();

print_r($users);

Upvotes: 14

Related Questions