ZF2 Doctrine Hydratation - php

When I query a table, for example:
$query = $this->entityManager->createQueryBuilder();
$query->select('TaPost')->from("Application\Entity\TaPost", 'TaPost');
return $query->getQuery()->getResult()
)
I get an array of object "Tapost".
Is there an easy way (and not ruining performance) to get an array of a given new class ? An equivalent to zend/db/sql:
new HydratingResultSet(new \Zend\Stdlib\Hydrator\ClassMethods(), new myNewClass())

Do you want to get directly array result? There are two way. You get an entity object which is \Application\Entity\TaPost. You can create a method to your entity like that
class TaPost {
// Your entity attributes and methods
// ...
public function toArray()
{
return array(
"id" => $this->getId(),
"title" => $this->getTitle(),
"description" => $this->getDescription(),
// ...
);
}
}
And use them them when your for loop.
Another solution is, you can use Doctrine HYDRATE_ARRAY
$results = $query->getQuery()->getResult( Doctrine\ORM\Query::HYDRATE_ARRAY );

Try to use doctrine hydrator instead of zend hydrator.
$model = new \Blog\Model\Post();
$hydrator = new \DoctrineModule\Stdlib\Hydrator\DoctrineObject($this->getEntityManager(), 'Blog\Model\Post');
$model = $hydrator->hydrate($array, $model);

thank you for your answer but that's not exactly my objective.
I'm trying to do the tutorial and, instead of zend-db-sql i'm using Doctrine.
I have a method findAll() which have to return an array of objects from class PostInterface based on a custom model (post).
With Doctrine, I get an array of TaPost (TaPost being an entity of Doctrine) but I need to return an array of Post.
How can I tell Doctrine to automatically hydrate Post and not TaPost ? will i need to made a foreach on my doctrine result and hydrate an object Post one by one ?
ps: with zned-sql, they do it when getting the result:
$resultSet = new HydratingResultSet(new \Zend\Stdlib\Hydrator\ClassMethods(), new \Blog\Model\Post());
return $resultSet->initialize($result);

Related

How to set doctrine associations?

I know that association property in entity is implements \Doctrine\Common\Collections\Collection. I know that in constructor such properties should be initialized:
$this->collection = new \Doctrine\Common\Collections\ArrayCollection()
I know that I can modify collections using ArrayCollection#add() and ArrayCollection#remove(). However I have a different case.
Suppose I have a new simple array of associative entities. Using existing methods I need to check every element in array: if entity collection has it. If no - add array element to entity collection. In addition to this, I need to check every element in entity collection. If any collection element is absent in new array, then I need to remove it from collection. So much work to do trivial thing.
What I want? To have the setProducts method implemented:
class Entity {
private $products;
// ... constructor
public function setProducts(array $products)
{
// synchronize $products with $this->products
}
}
I tried: $this->products = new ArrayCollection($products). However this makes doctrine remove all products and add those ones from $products parameter. I want similar result but without database queries.
Is there any built in solution in Doctrine for such case?
Edit:
I would like to have a method in ArrayCollection like fromArray which would merge elements in collections removing unneeded. This would just duplicate using add/remove calls for each element in collection argumen manually.
Doctrine collections do not have a "merge"-feature that will add/remove entities from an array or Collection in another Collection.
If you want to "simplify" the manual merge process you describe using add/remove, you could use array_merge assuming both arrays are not numeric, but instead have some kind of unique key, e.g. the entity's spl_object_hash:
public function setProducts(array $products)
{
$this->products = new ArrayCollection(
array_merge(
array_combine(
array_map('spl_object_hash', $this->products->toArray()),
$this->products->toArray()
),
array_combine(
array_map('spl_object_hash', $products),
$products->toArray()
)
)
);
}
You might want to use the product id instead of spl_object_hash as 2 products with the same id, but created as separate entities - e.g. one through findBy() in Doctrine and one manually created with new Product() - will be recognized as 2 distinct products and might cause another insert-attempt.
Since you replace the original PersistentCollection holding your previously fetched products with a new ArrayCollection this might still result in unneeded queries or yield unexpected results when flushing the EntityManager, though. Not to mention, that this approach might be harder to read than explicitly calling addElement/removeElement on the original Collection instead.
I would approach it by creating my own collection class that extends Doctrine array collection class:
use Doctrine\Common\Collections\ArrayCollection;
class ProductCollection extends ArrayCollection
{
}
In the entity itself you would initialise it in the __constructor:
public function __construct()
{
$this->products = new ProductCollection();
}
Here, Doctrine will you use your collection class for product results. After this you could add your own function to deal with your special merge, perhaps something:
public function mergeProducts(ProductCollection $products): ProductCollection
{
$result = new ProductCollection();
foreach($products as $product) {
$add = true;
foreach($this->getIterator() as $p) {
if($product->getId() === $p->getId()) {
$result->add($product);
$add = false;
}
}
if($add) {
$result->add($product);
}
}
return $result;
}
It will return a brand new product collection, that you can replace your other collection in the entity. However, if the entity is attached and under doctrine control, this will render SQL at the other end, if you want to play with the entity without risking database updates you need to detach the entity:
$entityManager->detach($productEntity);
Hopes this helps

Laravel: array to Model with relationship tree

I want to create an Eloquent Model from an Array() fetched from database which is already toArray() of some model stored in database. I am able to do that using this code:
$model = Admin::hydrate($notification->data);
$notification->data = [
"name" => "abcd"
"email" => "abcd#yahoo.com"
"verified" => 0
"shopowner_id" => 1
"id" => 86
"shopowner" => [
"id" => 1
"name" => "Owner1"
"email" => "owner1#owner.com"
]
];
But i can't access the $model->shopowner->name
I have to use $model->shopowner['name']
I want to use the same class of notification without any specific change to access the data.
If you want to access shopowner as a relationship, you have to hydrate it manually:
$data = $notification->data;
$model = Notification::hydrate([$data])[0];
$model->setRelation('shopowner', ShopOwner::hydrate([$data['shopowner']])[0]);
Solution:
Thanks to #Devon & #Junas. by combining their code I landed to this solution
$data = $notification->data;
$data['shopowner'] = (object) $data['shopowner'];
$model = Admin::hydrate([$data])[0];
I see this as an invalid use of an ORM model. While you could mutate the array to fit your needs:
$notification->data['shopowner'] = (object) $notification->data['shopowner'];
$model = Admin::hydrate($notification->data);
Your model won't be functional because 'shopowner' will live as an attribute instead of a relationship, so when you try to use this model for anything other than retrieving data, it will cause an exception.
You cannot access array data as object, what you can do is override the attribute and create an instance of the object in your model, so then you can use it like that. For example:
public function getShopownerAttribute($value)
{
return new Notification($value); // or whatever object here
}
class Notification {
public function __construct($data)
{
// here get the values from your array and make them as properties of the object
}
}
It has been a while since I used laravel but to my understanding once you use hydrate your getting a Illuminate\Database\Eloquent\Collection Object, which then holds Model classes.
These however could have attributes that are lazy loaded when nested.
Using the collections fresh method could help getting a Full database object as using load missing

How to manually create a new empty Eloquent Collection in Laravel 4

How do we create a new Eloquent Collection in Laravel 4, without using Query Builder?
There is a newCollection() method which can be overridden by that doesn't really do job because that is only being used when we are querying a set result.
I was thinking of building an empty Collection, then fill it with Eloquent objects. The reason I'm not using array is because I like Eloquent Collections methods such as contains.
If there are other alternatives, I would love to hear them out.
It's not really Eloquent, to add an Eloquent model to your collection you have some options:
In Laravel 5 you can benefit from a helper
$c = collect(new Post);
or
$c = collect();
$c->add(new Post);
OLD Laravel 4 ANSWER
$c = new \Illuminate\Database\Eloquent\Collection;
And then you can
$c->add(new Post);
Or you could use make:
$c = Collection::make(new Post);
As of Laravel 5. I use the global function collect()
$collection = collect([]); // initialize an empty array [] inside to start empty collection
this syntax is very clean and you can also add offsets if you don't want the numeric index, like so:
$collection->offsetSet('foo', $foo_data); // similar to add function but with
$collection->offsetSet('bar', $bar_data); // an assigned index
I've actually found that using newCollection() is more future proof....
Example:
$collection = (new Post)->newCollection();
That way, if you decide to create your own collection class for your model (like I have done several times) at a later stage, it's much easier to refactor your code, as you just override the newCollection() function in your model
Laravel >= 5.5
This may not be related to the original question, but since it's one of the first link in google search, i find this helpful for those like me, who are looking for how to create empty collection.
If you want to manually create a new empty collection, you can use the collect helper method like this:
$new_empty_collection = collect();
You can find this helper in Illuminate\Support\helpers.php
snippet:
if (! function_exists('collect')) {
/**
* Create a collection from the given value.
*
* #param mixed $value
* #return \Illuminate\Support\Collection
*/
function collect($value = null)
{
return new Collection($value);
}
}
Just to add on to the accepted answer, you can also create an alias in config/app.php
'aliases' => array(
...
'Collection' => Illuminate\Database\Eloquent\Collection::class,
Then you simply need to do
$c = new Collection;
In Laravel 5 and Laravel 6 you can resolve the Illuminate\Database\Eloquent\Collection class out of the service container and then add models into it.
$eloquentCollection = resolve(Illuminate\Database\Eloquent\Collection::class);
// or app(Illuminate\Database\Eloquent\Collection::class). Whatever you prefer, app() and resolve() do the same thing.
$eloquentCollection->push(User::first());
For more information about understanding resolving objects out of the service container in laravel take a look here:
https://laravel.com/docs/5.7/container#resolving
I am using this way :
$coll = new Collection();
$coll->name = 'name';
$coll->value = 'value';
$coll->description = 'description';
and using it as normal Collection
dd($coll->name);
It is better to use the Injection Pattern and after $this->collection->make([]) than new Collection
use Illuminate\Support\Collection;
...
// Inside of a clase.
...
public function __construct(Collection $collection){
$this->collection = $collection;
}
public function getResults(){
...
$results = $this->collection->make([]);
...
}
What worked for me was to name the use namespace and instantiate it directly:
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
# Usage
$this->latest_posts = new EloquentCollection();
Allowed me to merge two data subsets of eloquent collection results, this maintains the relationships - a regular collection (collect()) loses relationship and probably some more metadata.
$limit = 5;
$this->latest_posts = new EloquentCollection();
$pinned_posts = PinnedPostReference::where('category', $category)->get();
if($pinned_posts->count() > 0) {
foreach($pinned_posts as $ppost) {
$this->latest_posts->push($ppost->post);
}
}
# Another Eloquent result set ($regular_posts)
foreach($regular_posts as $regular_post) {
$this->latest_posts->push($regular_post);
}

Symfony2 Doctrine2 how to add object if not fetched from db

Check this example: http://symfony.com/doc/current/book/doctrine.html#fetching-objects-from-the-database
What I got is:
$results = $applicationsRepo->findByInName($appInNames);
Where appInNames is an array looking like this:
array(
app1_name => app1_name,
app2_name => app2_name,
app3_name => app3_name,
...
)
I want to create an entity object when it's not found. How to check if app1_name was returned and if not create one ?
If you need to get an array of all entities that have inName attribute set to either of app1_name, app2_name, etc with a default value if it does not exists, you can use findOneBy instead of findBy and create your entity if the result is NULL.
May not be the most efficient method because of the loop but it give you what you need :
$results = array();
foreach($appInNames as $appInName) {
$app = $applicationsRepo->findOneByInName($appInName);
if(!isset($app)) {
// Create you entity here
}
$results[$appInName] = $app;
}
A more efficient method may be to write a custom Repository and use the queryBuilder to add all your OR conditions. You'll get all the existing entities in one query, you will then have to parse the result to create the missing entities.

Change object type with hydrators, inside a pagination collection with ZendFramework 2 + Doctrine 2

I'm building a RESTful API, and I have problem.
The goal:
I want to hydrate a collection, that comes from a Paginator.
I mean, inside the collection, I don't want to return a Project object, I want to return a HalCollection of HalResources. To create these HalResources, I need to use the Project object (plus additional information).
The scenario:
I create a class ProjectHydrator, that implements HydratorInterface, with the two methods:
class ProjectHydrator implements HydratorInterface {
public function hydrate(array $data, $project){ .... }
public function extract($project) { .... }
}
I attach this Hydrator to my module, inside the module.config.php
'phlyrestfully' => array (
'renderer' => array (
'hydrators' => array (
'MYPROJECT\Entity\Project' => new \MYPROJECT\Hydrators\ProjectHydrator()
)
),
......
)
And the fetchAll of the listener method y create the pagination in this way:
$dql = 'SELECT e FROM \MYPROJECT\Entity\Project e';
$query = $this->getEntityManager()->createQuery($dql); // Class: \Doctrine\ORM\Query
$ormPaginator = new \Doctrine\ORM\Tools\Pagination\Paginator($query); //Class: Doctrine\ORM\Tools\Pagination\Paginator
$doctrinePaginator = new \DoctrineORMModule\Paginator\Adapter\DoctrinePaginator($ormPaginator); //Class: DoctrineORMModule\Paginator\Adapter\DoctrinePaginator
$paginator = new \Zend\Paginator\Paginator($doctrinePaginator); //Class: Zend\Paginator\Paginator
return $paginator;
The problem: The hydrator is being executed... but is called the method "extract", with parameter a Project object. In this method I must return and array, and this is my problem, I want to return a HalResource, not an array.
I want to use the hydrator to change the type of object, from Project object (project Entity object) to a HalResource. To build this HalResource, I want to use the Project object plus an array with other parameters.
What I am doing wrong?
Any ideas?
Thank you very much!
No need to extend the Doctrine Paginator, simply set your query hydration mode to hydrate array mode.
use Doctrine\ORM\Query;
use Doctrine\ORM\Tools\Pagination\Paginator as DoctrinePaginator;
use DoctrineORMModule\Paginator\Adapter\DoctrinePaginator as PaginatorAdapter;
use Zend\Paginator\Paginator;
$dql = 'SELECT e FROM \MYPROJECT\Entity\Project e';
$query = $this->getEntityManager()->createQuery($dql);
//Set query hydration mode
$query->setHydrationMode(Query::HYDRATE_ARRAY);
$paginator = new Paginator(new PaginatorAdapter(new DoctrinePaginator($query)));
return $paginator;
Hope it helps.

Categories