ZF2 hydrating multiple rows into class's property that should be array - php

In ZF2, suppose I have a query result from the database like this:
Name | Value
-----+-------
a | 1
a | 2
a | 3
b | 6
b | 1
b | 5
...
There is a class:
class SomeClass
{
protected $values;
// + getter and setter for $values
}
I want to hydrate SomeClass so that I have the values property as an array, like [1, 2, 3].
How to do this?
PS: I know the hydration is done with the HydratingResultSet(), but AFAIK, HydratingResultSet() hydrates one object per table row, whereas here I need to hydrate 1 object for several rows.
EDIT: after remarks from #newage, understood that the question wasn't well described.
I need to have the objects, instantiated from SomeClass, like a = new SomeClass() and b = new SomeClass() that will have the values variables filled with [1, 2, 3] for a, and [6, 1, 5] for b -- exactly what corresponds to a and b from the database query result.

HydratingResultSet will return a ResultSet. ResultSet implements Iterator interface. It return array of results.
If you need other collection class, you can write it.
For example:
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter'); // Get an adapter
$hydrator = new ClassMethods; // Create a hydrator.
$entity = Entity\User::class; // Entity class name
$prototype = new EntityCollection($hydrator, $entity);
$table = new TableGateway('table_name', $dbAdapter, null, $prototype);
If your entity with methods set... and get..., you need use ClassMethods hydrator. If entity with properties, need use ObjectProperty hydrator.
For creating a collection of entities. Need create a collection class, it need implement Iterator and ResultSetInterface
class EntityCollection implements Iterator, ResultSetInterface
{
protected $entities = [];
protected $hydrator;
protected $entityName;
public function __construct($hydrator, $entityName)
{
$this->hydrator = $hydrator;
$this->entityName = $entityName;
}
public function initialize($dataSource)
{
foreach ($dataSource as $dataRow) {
$this->append($this->hydrator->hydrate((array)$dataRow, new $this->entityName()));
}
}
public function append($entity)
{
array_push($this->entities, $entity);
return $this;
}
/* Need create all methods from Iterator */
...
}
UPDATE: after remark from #dima-dz.
You can read all data from DB and use foreach.
Like this. Example
$someObjectA = new SomeClass();
$someObjectB = new SomeClass();
$result = $table->select([]);
foreach ($result as $row) {
switch ($row->Name) {
case 'a':
$someObjectA->add($row->Value);
break;
case 'b':
$someObjectB->add($row->Value);
break;
}
}

#newage, posted a great solution, but I found out there's another possibility to do what I wanted, maybe a bit simpler. There's a php function parse_str() that can parse a string from Mysql field and create an array for the values variable if you properly format the content of the mysql field. An example of a mysql join with the ZF2 that creates the needed string for parsing is like this
->join(
['t' => 'Table',
't.Id = t1.Id',
[
'Values' =>
new Expression("GROUP_CONCAT(
DISTINCT CONCAT_WS('=', t1.Id, t1.Name) SEPARATOR '&')"
),
]
)

Related

Select specific fields from array of Eloquent collecton | Laravel

I have a collection object which includes the array of Model objects and I would like to select specific fields from the model.
Illuminate\Database\Eloquent\Collection Object
(
[items:protected] => Array
(
[0] => App\Model Object
[1] => App\Model Object
[2] => App\Model Object
)
)
Now I would like to select some fields from the model object. When I try to do the following syntax
print_r($collection->select('filed a', 'field b'));
then the following error occurs.
BadMethodCallException in Macroable.php line 74: Method select does
not exist.
I think select can work directly with the eloquent model but not with a collection.
Are you looking for only()
$filtered = $collection->only(['list', 'of', 'fields', 'to', 'keep']);
or perhaps mapWithKeys()
You are correct that select is not present on the Collection class.
What you can do is map, filter or transform the collection yourself e.g.
$whiteList = ['filed a', 'field b'];
$filledOnly = $collection->map(function ($item) use ($whiteList) {
$properties = get_object_vars($item);
foreach ($properties as $property) {
if(!in_array($property, $whiteList) {
unset($item->{property});
}
}
return $item;
});
The problem is in PHP once a property (or field) is set on an object, you really have to unset it or create new objects of the same class). This is why I came up with this solution.
Question is: How did you retrieve this collection in the first place, could you not add the select to the query itself?
The best would have been to select the fields you need before you execute the query on the model. However, you can use map() if you want to preserve the initial collection or transform() if you want to override the collection (for example):
$selected_fields = ['filed a', 'field b']
$models->map(function ($zn) use ($selected_fields) {
return $zn->newInstance(array_only($zn->getAttributes(), $selected_fields));
})->toArray();
newInstance() method creates a new empty instance of that model then getAttributes() retrieves the attributes present in the model. So the initial model is preserved in this process.
For reference sake, the implementation of newInstance() can be found on at Illuminate\Database\Eloquent\Model class and it is as follows (on Laravel 5.2):
/**
* Create a new instance of the given model.
*
* #param array $attributes
* #param bool $exists
* #return static
*/
public function newInstance($attributes = [], $exists = false)
{
// This method just provides a convenient way for us to generate fresh model
// instances of this current model. It is particularly useful during the
// hydration of new objects via the Eloquent query builder instances.
$model = new static((array) $attributes);
$model->exists = $exists;
return $model;
}

How to select discriminator column in doctrine 2

I need some help when select only discriminator column from doctrine 2 when run the DQL below
SELECT p.type FROM AppBundle\Entity\Product p
type is discriminator column in entity AppBundle\Entity\Product
#ORM\DiscriminatorColumn(name="type", type="smallint")`
#ORM\DiscriminatorMap({
"0" = "AppBundle\Entity\Product",
"1" = "AppBundle\Entity\Product\SingleIssue",
"2" = "AppBundle\Entity\Product\CountBasedIssue",
"3" = "AppBundle\Entity\Product\TimeBasedIssue"
})
I know that type is not a real property in entity, but is there anyway for me to do that?
Thanks in advance!
Updated
After 2 days for reading Doctrine codes, I decided to override SqlWalker and create new Hydrator by the snippets below
Override SqlWalker
<?php
namespace ...;
use Doctrine\ORM\Query\SqlWalker;
class CustomSqlWalker extends SqlWalker
{
const FORCE_GET_DISCRIMINATOR_COLUMN = 'forceGetDiscriminatorColumn';
const DISCRIMINATOR_CLASS_MAP = 'discriminatorClassMap';
/**
* {#inheritdoc}
*/
public function walkSelectClause($selectClause)
{
$sql = parent::walkSelectClause($selectClause);
$forceGetDiscriminatorColumn = $this->getQuery()->getHint(self::FORCE_GET_DISCRIMINATOR_COLUMN);
if (empty($forceGetDiscriminatorColumn)) {
return $sql;
}
foreach ($this->getQueryComponents() as $key => $queryComponent) {
if (!in_array($key, $forceGetDiscriminatorColumn)) {
continue;
}
$metadata = $queryComponent['metadata'];
$discriminatorColumn = $metadata->discriminatorColumn['name'];
$tableName = $metadata->table['name'];
$tableAlias = $this->getSQLTableAlias($tableName, $key);
$discriminatorColumnAlias = $this->getSQLColumnAlias($discriminatorColumn);
$sql .= ", $tableAlias.$discriminatorColumn AS $discriminatorColumnAlias";
}
return $sql;
}
}
Custom Hydrator
<?php
namespace ...;
use Doctrine\ORM\Internal\Hydration\ArrayHydrator;
use PDO;
class CustomHydrator extends ArrayHydrator
{
/**
* {#inheritdoc}
*/
protected function hydrateAllData()
{
$result = array();
$rootClassName = null;
if (isset($this->_hints['forceGetDiscriminatorColumn']) &&
isset($this->_hints['discriminatorClassMap'])) {
$rootClassName = $this->_hints['discriminatorClassMap'];
}
while ($data = $this->_stmt->fetch(PDO::FETCH_ASSOC)) {
foreach ($data as $key => $value) {
if ($this->hydrateColumnInfo($key) != null ||
empty($rootClassName)) {
continue;
}
$metadata = $this->getClassMetadata($rootClassName);
$discriminatorColumn = $metadata->discriminatorColumn;
$fieldName = $discriminatorColumn['fieldName'];
$type = $discriminatorColumn['type'];
$this->_rsm->addScalarResult(
$key, $fieldName, $type
);
}
$this->hydrateRowData($data, $result);
}
return $result;
}
}
Configure custom hydrator
orm:
...
hydrators:
CustomHydrator: YourNamespace\To\CustomHydrator
Final step
$query = $queryBuilder->getQuery();
$query->setHint(\Doctrine\ORM\Query::HINT_CUSTOM_OUTPUT_WALKER, 'YourNamespace\To\CustomSqlWalker');
$query->setHint(\YourNamespace\To\CustomSqlWalker::FORCE_GET_DISCRIMINATOR_COLUMN, array($rootAlias)); // this alias will be used in CustomSqlWalker class
$query->setHint(\YourNamespace\To\CustomSqlWalker::DISCRIMINATOR_CLASS_MAP, $this->getClassName()); // this full-qualify class name will be used in CustomHydrator class
$products = $query->getResult('CustomHydrator');
TL;DR
I know this is a very complicated solution (may be just for my scenario), so I hope someone could give me another simple way to fix that, thanks so much!
There is no direct access to the discriminator column.
It may happen that the entities of a special type should be queried.
Because there is no direct access to the discriminator column,
Doctrine provides the INSTANCE OF construct.
You can query for the type of your entity using the INSTANCE OF DQL as described in the docs. As example:
$query = $em->createQuery("SELECT product FROM AppBundle\Entity\AbstractProduct product WHERE product INSTANCE OF AppBundle\Entity\Product");
$products = $query->getResult();
Hope this helps
I use this little "hack"
Define a common interface for your entities (optional but recommended)
Create a getType method in this interface
Create constant into Discriminator entity
Return the proper constant inside every discriminated entity
That way you can retrieve the discriminator "generic" entity (Product in your case) and call getType onto it.
Of course if you're interested into result filtering done directly by sql, this is not a solution at all and, I'm afraid, there isn't any solution available at the moment.
If you find one better that this, please share with us.
You should be able to do this with a scalar result with INSTANCE OF and a case, when, (else,) end clause:
SELECT
(case
when p INSTANCE OF AppBundle\Entity\Product then \'0\'
when p INSTANCE OF AppBundle\Entity\Product\SingleIssue then \'1\'
when p INSTANCE OF AppBundle\Entity\Product\CountBasedIssue then \'2\'
when p INSTANCE OF AppBundle\Entity\Product\TimeBasedIssue then \'3\'
else \'foobar\'
end) as type
FROM
AppBundle\Entity\Product p
Of course the disadvantage is you have to update the query every time you add a DiscriminatorMap entry.

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

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.

Dynamically create PHP object based on string

I would like to create an object in PHP based on a type defined by a string in a MySQL database. The database table has columns and sample data of:
id | type | propertyVal
----+------+-------------
1 | foo | lorum
2 | bar | ipsum
...with PHP data types
class ParentClass {...}
class Foo extends ParentClass {private $id, $propertyVal; ...}
class Bar extends ParentClass {private $id, $propertyVal; ...}
//...(more classes)...
Using only one query, I would like to SELECT a row by id and create an object of the type define the table's type column with other columns in the SELECTed row being assigned to the newly created object.
I was thinking that using:
mysql_fetch_object()
Reading the type attribute
Creating an object with type defined by type attribute
But know of no way to dynamically create a type based on a string. How does one do this?
But know of no way to dynamically create a type based on a string. How does one do this?
You can do it quite easily and naturally:
$type = 'myclass';
$instance = new $type;
If your query is returning an associative array, you can assign properties using similar syntax:
// build object
$type = $row['type'];
$instance = new $type;
// remove 'type' so we don't set $instance->type = 'foo' or 'bar'
unset($row['type']);
// assign properties
foreach ($row as $property => $value) {
$instance->$property = $value;
}
There's a very neat syntax you can use that I learned about a couple of months ago that does not rely on a temporary variable. Here's an example where I use a POST variable to load a specific class:
$eb = new ${!${''} = $_POST['entity'] . 'Binding'}();
In your specific case though, you would be able to solve it by using PDO. It has a fetch mode that allows the first column's value to be the class the row instantiates into.
$sth->fetch(PDO::FETCH_CLASS | PDO::FETCH_CLASSTYPE);
$instance = new $classname; // i.e. $type in your case
Works very well...
Below is what I was looking for when I came to this thread. use {"objectName"} (brackets) to declare or reference the object name in the form of a string.
$gameData = new stdClass();
$gameData->location = new stdClass();
$basementstring = "basement";
class tLocation {
public $description;
}
$gameData->location->{'darkHouse'} = new tLocation;
$gameData->location->{"darkHouse"}->description = "You walkinto a dusty old house";
$gameData->location->{$basementstring} = new tLocation;
$gameData->location->{"basement"}->description = "its really damp down here.";
//var_dump($gameData);
echo $gameData->location->basement->description;
This way of referring to the object seems to be interchangeable. I couldn't find the answer so i had to fool around with it Until I found a way.
as silkfire says, this can be achieved by using PDO specific modes, so here is an example. Using your same database values and defined objects:
id | type | propertyVal
----+------+-------------
1 | foo | lorum
2 | bar | ipsum
class ParentClass {...}
class Foo extends ParentClass {private $id, $propertyVal; ...}
class Bar extends ParentClass {private $id, $propertyVal; ...}
//...(more classes)...
with a single query (you must name the field containing the class name first):
$stmt = $db->prepare('SELECT type,id,propertyVal FROM table WHERE id=1');
$stmt->execute();
$foo = $stmt->fetch(PDO::FETCH_CLASS | PDO::FETCH_CLASSTYPE);
var_dump($foo); // $foo is a newly created object of class foo, with properties named like and containing the value of subsequent fields
this is cool but it gets cooler with a while
$stmt = $db->prepare('SELECT type,id,propertyVal FROM table');
$stmt->execute();
while ($object = $stmt->fetch(PDO::FETCH_CLASS | PDO::FETCH_CLASSTYPE))
{var_dump($object);} // here all desired objects, dynamically constructed accordingly to the first column returned by the query
you can define a constructor (which will be called after the values from database are assigned to properties) to work on those dynamically assigned properties, say by replacing a string with it's uppercased value
class foo
{function __construct ()
{$this->uper = strtoupper($this->propertyVal);}}

Categories