In a Zend Framework application, that is built pretty similar to the album application from the ZF2 Getting Started tutorial, I use a ResultSet object to transport the data from the model over the controller to the view (for details see the code below).
This works fine, but I don't get, where the ResultSet object holds the data. I can loop it e.g. with foreach and get the data row byrow (or better model object by model object). But when I debug it in my IDE or simply with var_dump(...), it seems to be empty.
How/where does a Zend\Db\ResultSet\ResultSet object hold the data, retrieved from the database?
The relevant parts of the code:
Module settings:
class Module implements ConfigProviderInterface, ServiceProviderInterface, AutoloaderProviderInterface {
...
public function getServiceConfig() {
try {
return array (
'factories' =>array(
...
'SportTable' => function ($serviceManager) {
$tableGateway = $serviceManager->get('SportTableGateway');
$table = new SportTable($tableGateway);
return $table;
},
'SportTableGateway' => function ($serviceManager) {
$dbAdapter = $serviceManager->get('Zend\Db\Adapter\Adapter');
$resultSetPrototype = new ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new Sport());
return new TableGateway('sports', $dbAdapter, null, $resultSetPrototype);
},
...
)
);
}
...
}
Model (table):
class CourseTable {
...
public function findAllByCityNameAndSportTitle($cityName, $sportTitle) {
$select = new Select();
$where = new Where();
...
$resultSet = $this->tableGateway->selectWith($select);
return $resultSet;
}
...
}
Model (mapper):
class Course implements ArraySerializableInterface {
public $id;
...
public function exchangeArray(array $data) {
$this->id = (isset($data['id'])) ? $data['id'] : null;
...
}
...
}
Controller:
class CatalogController extends AbstractActionController {
...
public function listCoursesAction() {
$cityName = $this->params()->fromRoute('city', null);
$sportTitle = $this->params()->fromRoute('sport', null);
return new ViewModel(array(
'city' => $cityName,
'sport' => $sportTitle,
'courses' => $this->getCourseTable()->findAllByCityNameAndSportTitle($cityName, $sportTitle)
));
}
...
}
View:
<?php foreach ($courses as $course) : ?>
<div>
<div>id: <?php echo $this->escapeHtml($course->id); ?></div>
...
</div>
<?php endforeach; ?>
Zend\Db\ResultSet\ResultSet or rather Zend\Db\ResultSet\AbstractResultSet is holding the answer to your question. This object has 10-12 methods most of them providing iterative functionality. To answer your question the Zend\Db\ResultSet\ResultSet holds the information retrieved from the Db in its dataSource paramter (Zend\Db\ResultSet\ResultSet::$dataSource).
The paradox of results being iterated and loaded with foreach but not seen by var_dump() can be explained by the fact that the results returned are actually hold in buffered.
If you want to access the result set I suggest you to use Zend\Db\ResultSet\ResultSet::toArray() ($resultSet->toArray()). This will return an array of arrays where each array is a row in the DB table.
Hope this helps, feedback will be appreciated :),
Stoyan.
Related
I wrote an api with a function that sets notification as read by passing it's id.
But also, there should be an option to pass array of ids there, to mark several at once as read. I should extend function so that it handles the case where $this>data['id'] is an array.
Is this the right way?
My Service:
public function read($id = []){
$notification = $this->getRepository()->findBy([
'id' => $id
]);
if($notification) {
$notification[0]->setRead(new \DateTime());
$this->em->flush();
}
}
My Controller:
public function readAction()
{
$this->requirePostParams(['id']);
$this->get('app')->read(
$this->data['id']
);
return $this->success();
}
You can indeed pass an array of id values to \Doctrine\ORM\EntityRepository::findBy(); e.g:
$notifications = $this->getRepository()->findBy([
'id' => [1, 2, 3] // etc.
]);
However, since findBy() can return multiple results, it will return an array (or array-like object like Doctrine\ORM\PersistentCollection). Therefore you should iterate over your result set:
foreach ($notifications as $notification) {
$notification->setRead(new \DateTime());
}
$this->em->flush();
Additionally, it's a matter of taste to some degree but you may want to make your API more explicit and create separate methods for a single action versus a group action; e.g:
public function read(int $id)
{
//in this scenario you are searching for one notification
// only so you can use `findOneBy()` instead
$notification = $this->getRepository()->findOneBy(['id' => $id]);
$notification->setRead(new \DateTime());
$this->em->flush();
}
public function readMany(array $ids)
{
$notification = $this->getRepository()->findBy(['id' => $ids]);
foreach ($notifications as $notification) {
$notification->setRead(new \DateTime());
}
$this->em->flush();
}
As pointed out by #Yoshi, read() could also be neatly implemented as:
public function read(int $id)
{
$this->readMany([$id]);
}
Hope this helps :)
I am new to Laravel. I am trying to use Eloquent Model to access data in DB.
I have tables that shares similarities such as table name.
So I want to use one Model to access several tables in DB like below but without luck.
Is there any way to set table name dynamically?
Any suggestion or advice would be appreciated. Thank you in advance.
Model:
class ProductLog extends Model
{
public $timestamps = false;
public function __construct($type = null) {
parent::__construct();
$this->setTable($type);
}
}
Controller:
public function index($type, $id) {
$productLog = new ProductLog($type);
$contents = $productLog::all();
return response($contents, 200);
}
Solution For those who suffer from same problem:
I was able to change table name by the way #Mahdi Younesi suggested.
And I was able to add where conditions by like below
$productLog = new ProductLog;
$productLog->setTable('LogEmail');
$logInstance = $productLog->where('origin_id', $carrier_id)
->where('origin_type', 2);
The following trait allows for passing on the table name during hydration.
trait BindsDynamically
{
protected $connection = null;
protected $table = null;
public function bind(string $connection, string $table)
{
$this->setConnection($connection);
$this->setTable($table);
}
public function newInstance($attributes = [], $exists = false)
{
// Overridden in order to allow for late table binding.
$model = parent::newInstance($attributes, $exists);
$model->setTable($this->table);
return $model;
}
}
Here is how to use it:
class ProductLog extends Model
{
use BindsDynamically;
}
Call the method on instance like this:
public function index()
{
$productLog = new ProductLog;
$productLog->setTable('anotherTableName');
$productLog->get(); // select * from anotherTableName
$productLog->myTestProp = 'test';
$productLog->save(); // now saves into anotherTableName
}
I created a package for this: Laravel Dynamic Model
Feel free to use it:
https://github.com/laracraft-tech/laravel-dynamic-model
This basically allows you to do something like this:
$foo = App::make(DynamicModel::class, ['table_name' => 'foo']);
$foo->create([
'col1' => 'asdf',
'col2' => 123
]);
$faz = App::make(DynamicModel::class, ['table_name' => 'faz']);
$faz->create([...]);
I am stuck on updating a eagerloaded model that has a "hasMany" relation.
I have one model like so:
class UserGroup extends Model
{
public function enhancements()
{
return $this->hasMany('App\UserGroupEnhancement');
}
}
My controller is passing $userGroup to the view like so:
$userGroup = $this->userGroup->with('enhancements')->whereId($id)->first();
and then in my view I have
#foreach($userGroup->enhancements as $enhancement)
<label>{{$enhancement->type}}</label>
<input class="form-control" name="enhancements[{{$enhancement->id}}][price]" value="{{$enhancement->price}}">
#endforeach
When updating, how do I update all of records in the enhancement relationship? It's passed back into multiple arrays. I am currently doing something like this.
public function update($id)
{
$userGroup = $this->userGroup->findOrFail($id);
$enhancement = \Input::get('enhancements');
if (is_array($enhancement)) {
foreach ($enhancement as $enhancements_id => $enhancements_price) {
$userGroup->enhancements()->whereId($enhancements_id)->update($enhancements_price);
}
}
}
Is there a way I can do this without needing the foreach loop? I see the push() method, but seems to only work on a single array.
There isn't a better way to do this. There is an Eloquent method called saveMany but it is used to create new records and not update. ExampleDoc:
$comments = [
new Comment(['message' => 'A new comment.']),
new Comment(['message' => 'Another comment.']),
new Comment(['message' => 'The latest comment.'])
];
$post = Post::find(1);
$post->comments()->saveMany($comments);
I would stick with your solution, you can even create a trait or a base Eloquent class and put that logic in a method so it can be used by all other models, if you ever need to.
Something like:
trait UpdateMany {
public function updateMany($updates, $relationshipName)
{
if (!empty($updates)) {
foreach ($updates as $update_id => $update) {
$this->{$relationshipName}()->whereId($update_id)->update($update);
}
}
}
}
Then attach to your model(s):
class UserGroup extends Model
{
use UpdateMany;
public function enhancements()
{
return $this->hasMany('App\UserGroupEnhancement');
}
}
And simply use as:
$userGroup = $this->userGroup->findOrFail($id);
$userGroup->updateMany(\Input::get('enhancements'), 'enhancements');
i'm new in Zend Framework. I'm working with an application that uses the following db pattern: Controler -> Service -> Mapper.
And i have an entity for each table and a hydrator file. What i need is to join tables in SQL query, but i don´t know how to access the results from the joined table, cause it only feeds the entity of the current table. Here´s my code:
Controller:
$userEntity = new \Portal\Entity\Iamuserdb();
$userEntity->setIamUserDbId($this->params('id'));
$service = $this->getServiceLocator()->get('Portal\Service\Reports');
$user = $service->getUserById($userEntity)
Service (Reports):
protected $iamuserMapper;
public function selectUsers(){
$usuarios = $this->getIamUserMapper()->fetchAllUsers();
return $usuarios;
}
protected function getIamUserMapper()
{
if ($this->iamuserMapper == null) {
$this->iamuserMapper = $this->getServiceManager()->get('Portal\Mapper\Iamuser');
}
return $this->iamuserMapper;
}
Mapper (Iamuser):
public function fetchAllUsers()
{
$select = $this->getSelect();
$select->from('IAMUser')
->join('IAMUser_IAMGroup', 'IAMUser.iamUserDbId=IAMUser_IAMGroup.IAMUser_iamUserDbId', array('IAMUser_iamUserDbId', 'iamGroups_iamGroupDbId'), 'left')
->join('IAMGroup', 'IAMGroup.iamGroupDbId=IAMUser_IAMGroup.iamGroups_iamGroupDbId', array(), 'left');
return $this->select($select);
}
Hydrator:
class Iamuser extends Hydrator
{
protected function getEntity()
{
return 'Portal\Entity\Iamuserdb';
}
protected function getMap()
{
return array(
);
}
}
In my Module.php from the module i have the factory:
'factories' => array(
'Portal\Mapper\Iamuser' => function($sm) {
$dbAdapter = new \Zend\Db\Adapter\Adapter($sm->get('Configuration')['db']);
$mapper = new Mapper\Iamuser();
$mapper->setEntityPrototype(new Entity\Iamuserdb())
->setHydrator(new Mapper\Hydrator\Iamuser())
->setDbSlaveAdapter($dbAdapter)
->setDbAdapter($dbAdapter);
return $mapper;
},
}
See in my mapper i'm joining two tables, but how to fetch these results, since they came from another table and is not feed in Entity class? Thanks in advance
I Solve my problem. All i have to do is return this statement:
return $this->select($select)->getDataSource();
So i skip the hydration results in entities and get raw PDO array, this way i can join tables.
I'm developing a RESTful ZF2 based application and using a TableGateway implementation (subclass of the Zend\Db\TableGateway) in combination with a simple mapper for the model, similar to the Album example of the ZF2 manual.
Table class
<?php
namespace Courses\Model;
use ...
class CourseTable {
protected $tableGateway;
public function __construct(TableGateway $tableGateway) {
$this->tableGateway = $tableGateway;
}
public function findOnceByID($id) {
$select = new Select();
$where = new Where();
$select->columns(array(
'id',
'title',
'details',
));
$select->from($this->tableGateway->getTable());
$select->where($where, Predicate::OP_AND);
$resultSet = $this->tableGateway->selectWith($select);
return $resultSet;
}
}
Mapper class
<?php
namespace Courses\Model;
use ...
class CourseDetails implements ArraySerializableInterface {
public $id;
public $title;
public $details;
public function exchangeArray(array $data) {
$this->id = (isset($data['id'])) ? $data['id'] : null;
$this->title = (isset($data['title'])) ? $data['title'] : null;
$this->details = (isset($data['details'])) ? $data['details'] : null;
}
public function getArrayCopy() {
return get_object_vars($this);
}
}
Controller
<?php
namespace Courses\Controller;
use ...
class CoursesController extends RestfulController // extends AbstractRestfulController
{
protected $acceptCriteria = array(
'Zend\View\Model\JsonModel' => array(
'application/json',
),
'Zend\View\Model\FeedModel' => array(
'application/rss+xml',
),
);
private $courseTable;
public function get($id)
{
$course = $this->getCourseTable()->findOnceByID($id)->current();
$viewModel = $this->acceptableViewModelSelector($this->acceptCriteria);
$viewModel->setVariables(array('data' => array(
'id' => $courseDetails->id,
'title' => $courseDetails->title,
'details' => $courseDetails->details
)));
return $viewModel;
}
...
}
It's working for a shallow output like this:
{
"data":{
"id":"123",
"title":"test title",
"details":"test details"
}
}
But now I need a multidimensional output with nested lists like this:
{
"data":{
"id":"123",
"title":"test title",
"details":"test details",
"events":{
"count":"3",
"events_list":[ <- main list
{
"id":"987",
"date":"2013-07-20",
"place":"Berlin",
"trainers":{
"count":"1",
"trainers_teamid":"14",
"trainers_teamname":"Trainers Team Foo",
"trainers_list":[ <- nested list
{
"id":"135",
"name":"Tom"
}
]
}
},
{
"id":"876",
"date":"2013-07-21",
"place":"New York",
"trainers":{
"count":"3",
"trainers_teamid":"25",
"trainers_teamname":"Trainers Team Bar",
"trainers_list":[ <- nested list
{
"id":"357",
"name":"Susan"
},
{
"id":"468",
"name":"Brian"
},
{
"id":"579",
"name":"Barbara"
}
]
}
},
{
"id":"756",
"date":"2013-07-29",
"place":"Madrid",
"trainers":{
"count":"1",
"trainers_teamid":"36",
"trainers_teamname":"Trainers Team Baz",
"trainers_list":[ <- nested list
{
"id":"135",
"name":"Sandra"
}
]
]
}
]
}
}
}
How / where should I assemble the data to this structure? Directly in the mapper, so that it contains the whole data? Or should I handle this with multiple database requests anb assemple the structure in the controller?
What you are trying to accomplish has nothing to do with the TableGateway-Pattern. The TableGateway-Pattern is there to gain access to the Data of one specified Table. This is one of the reasons why in ZF2 you no longer have the option to findDependantRowsets(). It's simply not the TableGateways Job to do so.
To achieve what you are looking for you have pretty much two options:
1. Joined Query
You could write a big query that joins all respective tables and then you'd manually map the output into your desired JSON Format.
2. Multiple Queries
A little less performant approach (looking at the SQL side of things) but "easier" to "map" into your JSON Format.
To give some insight, Doctrine would go with the multiple query approach by default. This is mostly (i guess!) done to provide features that would work on every data backend possible rather than just a couple of SQL Versions...
Service Class
Since you're wondering about the assembling of the json / array, i would set it up like this
'service_manager' => array(
'factories' => array(
'MyEntityService' => 'Mynamespace\Service\Factory\MyEntityServiceFactory'
)
)
// MyEntityServiceFactory.php
// assuming you only need one dependency! more lines for more dependencies ;)
class MyEntityServiceFactory implements FactoryInterface {
public function createService(ServiceLocatorInterface $serviceLocator) {
return new MyEntityService($serviceLocator->get('YourTableGateway'));
}
}
// Your SERVICE Class
class MyEntityService {
// do constructor and stuff to handle dependency
public function someBigQueryAsArray() {
// Query your Gateway here and create the ARRAY that you want to return,
// basically this array should match your json output, but have it as array
// to be used for other stuff, too
}
}
// lastly your controller
public function someAction() {
$service = $this->getServiceLocator()->get('MyEntityService');
$data = $service->someBigQueryAsArray();
// do that viewmodel selector stuff
// ASSUMING $data is a array of more than one baseObject
// i did this in my app to produce the desired valid json output, there may be better ways...
if ($viewModel instanceof JsonModel) {
foreach($data as $key => $value) {
$viewModel->setVariable($key, \Zend\Json\Json::encode($value));
}
return $viewModel;
}
// Handle other ViewModels ....
}