I mostly use zend_db_table with a paginator, the problem is that it will return zend_db_rows instead the domain objects from my datamapper.
Let's say :
class Content_Model_ArticleMapper {
/*
* #param Zend_Db_Select $select
* #return Zend_Paginator
*/
public function getPaginator($select = null){}
}
I can hack it by overriding _loadAndReturnRow method in a custom rowset
However this is pretty ugly as I don't have a Zend_Db_Row anymore when I query the table.
And loose the methods too like save which I don't want to replicate on the domain object.
:
class Content_Model_DbTable_Rowset_Articles extends Zend_Db_Table_Rowset {
protected function _loadAndReturnRow($position)
{
if (!isset($this->_data[$position])) {
require_once 'Zend/Db/Table/Rowset/Exception.php';
throw new Zend_Db_Table_Rowset_Exception("Data for provided position does not exist");
}
// do we already have a row object for this position?
if (empty($this->_rows[$position])) {
$this->_rows[$position] = new Content_Model_Article($this->_data[$position]);
}
// return the row object
return $this->_rows[$position];
}
}
So my question how do you do this nicely ? :) Do you write custom Paginator adapters?
You can set a rowClass in your DbTable like
DbTable
class Content_Model_DbTable_Article extends Zend_Db_Table_Abstract {
protected $_name = 'article';
public function init() {
$this->setRowClass('Content_Model_Article');
}
}
Domain Model
class Content_Model_Article extends Zend_Db_Table_Row {
//for example
public function getAuthorFullName() {
return $this->author_firstname . ' ' . $this->author_lastname;
}
}
Now rows in your rowset are instances of Content_Model_Article and you can use the Zend_Paginator_Adapter_Iterator.
Using Paginator
$articleTable = new Content_Model_DbTable_Article();
$articleRowset = $articleTable->fetchAll();
$paginator = new Zend_Paginator(Zend_Paginator_Adapter_Iterator($articleRowset));
//now you can loop through the paginator
foreach($paginator as $article) {
echo $article->getAuthorFullName();
}
Related
I have made a REST controller with Yii2 framework. When I try to retrieve a record from my database through an ActiveRecord model, JsonFormatter give me only real attributes. How can configure JsonFormatter to give me also public variable?
This is my code:
Controller
class MyController extends yii\rest\ActiveController
{
...
public function actionView($id)
{
$struct = \common\models\Struct::find()->where(['id' => '285'])->One();
if ($struct) {
return $struct;
}
return false;
}
}
Model
/**
* property string $id;
* property string $name;
*/
class Struct extends \yii\db\ActiveRecord
{
public $test;
...
public function afterFind()
{
parent::afterFind();
$this->test = 'ok';
}
}
result of request
{"id":1,"name": "ciccio"}
but if I print variable with print_r(), I have all object
\app\models\Struct object
(
[test] => ok
[_attributes:yii\db\BaseActiveRecord:private] => Array
(
[id] => 1
[name] => ciccio
)
)
How can I get the variable test property without add an empty field on my database table?
You can override the ActiveRecord::fields() method to add the custom field that is declared as the public property of the class. The fields() method returns the names of the columns whose values have been populated into this record.
Looking at your code you are trying to set the test property inside the afterFind() and want that value to be reflected against all rows when you call the Model::find() method. If that is correct then add the following inside your model:
public function fields() {
$fields = parent::fields();
$fields['test'] = 'test';
return $fields;
}
Now when you call the \common\models\Struct::find() it will return
{"id":1,"name": "ciccio","test":"ok"}
Try this:
keep all you showed and then Override getAttributes
public function getAttributes($names = null, $except = [])
{
return array_merge(['test'=>$this->test], parent::getAttributes($names, $except));
}
and in your controller isted of return like this: return $struct;
do it like this: return $struct->attributes;
So I have a model which I am filling from JSON. If I just return the filled model from the controller all is working as expected. However, if I try to access/return/use a specific property of the model it returns the default value. I feel like I must be missing something basic here. Any help is appreciated.
class parent extends Model
{
$id = -1;
$child;
protected $fillable = ['id','child'];
public function __construct()
{
$child = new child();
}
}
class child extends Model
{
$id = -1;
protected $fillable = ['id'];
}
Then in the controller method
public function doStuff(Request $request)
{
$data = json_decode($request->input('parent'),true);
$newParent = new parent();
$newParent->fill($data);
return $newParent; //has data as expected from posted json data which includes a parent with a child underneath it and data set on both
return $newParent->id; //has -1 rather than passed in value
}
Figured it out. By explicitly adding variables with the same name as the desired attributes those variables weren't getting filled but the attribute bag was. This is what comes of transitioning from a C#/strongly typed world I guess.
I have made a service to get Doctrine connection in my models (Not sure if it is a nice approach but I dont want to pass connection from controller to model constructor each time).
So lets say I want products in my controller
public function getProductsAction(Request $request) {
$product_model = new ProductModel();
return $product_model->getProducts();
}
I have Product model Which will access a helper to get "database_connection"
use AppBundle\Helper\ContainerHelper;
class ProductModel {
function getProducts() {
$helper = new ContainerHelper();
$db = $helper->getDoctrine();
$query = "SELECT * FROM customer_products;";
$statement = $db->prepare($query);
$statement->execute();
$result = $statement->fetchAll(PDO::FETCH_ASSOC);
return $result;
}
}
Now this helper is defined in src/AppBundle/Helper/ContainerHelper.php
namespace AppBundle\Helper;
use Symfony\Component\DependencyInjection\ContainerInterface as Container;
class ContainerHelper {
private $container;
public function __construct(Container $container) {
$this->container = $container;
}
public static function getDoctrine() {
$database_connection = $this->container->get('database_connection');
return $database_connection;
}
}
Lets say this service needs "service container" so in app/config/services.yml
services:
app.container_helper:
class: AppBundle\Helper\ContainerHelper
arguments: ['#service_container']
But it gives me error:
Catchable Fatal Error: Argument 1 passed to
AppBundle\Helper\ContainerHelper::__construct() must implement
interface Symfony\Component\DependencyInjection\ContainerInterface,
none given, called in \src\AppBundle\Model\ProductModel.php
on line 148 and defined
While I believe that I have implemented it correctly according to http://symfony.com/doc/current/book/service_container.html and http://anjanasilva.com/blog/injecting-services-in-symfony-2/, its certain that I have missed something or just got the whole bad idea. I need to know if it is a correct concept or what I have missed
While #pavlovich is trying to fix your existing code, I really think you are making this much more convoluted than it has to be. ProductModel itself should be a service with your database connection injected into it.
class ProductModel {
public function __construct($conn) {
$this->conn = $conn;
}
public function getProducts() {
$stmt = $this->conn->executeQuery('SELECT * FROM customer_products');
return $stmt->fetchAll();
}
services:
product_model:
class: AppBundle\...\ProductModel
arguments: ['#database_connection']
// controller.php
$productModel = $this->get('product_model'); // Pull from container
$products = $productModel->getProducts();
Rather than using helpers, I'd recommend using constructor injection and autowiring. It's more safe, future proof and easier to extend and test.
In such case, you'd have to create ProductRepository (more common and standard name for ProductModel) and pass it to controller.
1. Controller
<?php
class SomeController
{
/**
* #var ProductRepository
*/
private $productRepository;
public function __construct(ProductRepository $productRepository)
{
$this->productRepository = $productRepository;
}
public function getProductsAction()
{
return $this->productRepository->getProducts();
}
}
If you have difficulties to register controller as a service, just use Symplify\ControllerAutowire bundle.
2. ProductRepository
// src/AppBundle/Repository/ProductRepository.php
namespace AppBundle\Repository;
class ProductRepository
{
/**
* #var Doctrine\DBAL\Connection
*/
private $connection;
public function __construct(Doctrine\DBAL\Connection $connection)
{
$this->connection = $connection;
}
public function fetchAll()
{
$query = "SELECT * FROM customer_products;";
$statement = $this->connection->prepare($query);
$statement->execute();
return $statement->fetchAll(PDO::FETCH_ASSOC);
}
}
3. Service registration
# app/cofig/servies.yml
services:
product_repository:
class: AppBundle\Repository\ProductRepository
autowire: true
For more you can see similar question with answer here: Symfony 3 - Outsourcing Controller Code into Service Layer
With new version of Symfony 3.3, a new feature is added (Auto-wired Services Dependencies)
https://symfony.com/doc/current/service_container/autowiring.html
https://symfony.com/doc/current/service_container/3.3-di-changes.html
Using this feature, I solved this issue in following way:
Added a new directory /src/AppBundle/Model
Added my model classes in this directory
namespace AppBundle\Modal;
use Doctrine\ORM\EntityManagerInterface;
class ProductModal
{
private $em;
// We need to inject this variables later.
public function __construct(EntityManagerInterface $entityManager)
{
$this->em = $entityManager;
}
// We need to inject this variables later.
public function getProducts()
{
$statement = $this->em->getConnection()->prepare("SELECT * FROM product WHERE 1");
$statement->execute();
$results = $statement->fetchAll();
return $results;
}
}
Added in my app/config/services.yml
AppBundle\Modal\:
resource: '../../src/AppBundle/Modal/*'
public: true
In my controller I can use it like
$products = $this->get(ProductModal::class)->getProducts();
P.S. Dont forget to add use AppBundle\Entity\Product\Product; in controller
I am trying to save data from the form using yii2 rest api controller, standard create action. New line is created in DB, and id is returned, but data from POST is not saved - only zeroes appear in DB, then I overrided create action by my own one, the same situation. But if I directly save to DB, without rest api controller, data is saved successfully.
What can be a reason of such strange saving to DB? Thanks!
By the way, in index I can see post data in the format: {"_csrf":"wergferw","table_name":{"sum":25000,"currency":1}}
Controller is very simple:
namespace frontend\controllers;
use yii;
use yii\rest\ActiveController;
use yii\web\Response;
class DemandController extends ActiveController
{
public $modelClass = 'frontend\models\Demands';
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['contentNegotiator']['formats']['application/json']
= Response::FORMAT_JSON;
return $behaviors;
}
}
I get reply in rest:
<response><id>37</id></response>
Model is generated by gii
namespace frontend\models;
use Yii;
class Demands extends \yii\db\ActiveRecord
{
public static function tableName()
{
return 'demands';
}
public function rules()
{
return [
[['sum', 'currency'], 'required'],
];
}
public function attributeLabels()
{
return [
'sum' => 'Sum',
'currency' => 'Currency',
];
}
}
If none of the posted data appears in the database, it sounds like a post format issue to me. Check that your HTTP Post headers have "Content-Type: application/x-www-form-urlencoded".
I am also having the problem so after changing this line it's worked for me..
You try to change the model->load() method inside yii/rest/createAction
public function run()
{
$model->load(Yii::$app->getRequest()->getBodyParams(), '');
change this line to
$model->load(Yii::$app->getRequest()->getBodyParams());
}
see this link for more info - http://www.yiiframework.com/doc-2.0/yii-base-model.html#load()-detail
The model->load($data,$formName) - the $data should be $_GET or $_POST value of array and $formName use to load the data into the model. If not set, formName() is used. so u have to change the model->load() in createAction class.
Better you have to override the activecontroller default create action then try
class CabController extends ActiveController
{
public $modelClass = 'api\modules\v1\models\Cab';
public function actions(){
$actions = parent::actions();
unset($actions['create']);
unset($actions['update']);
unset($actions['index']);
return $actions;
}
/* Declare methods supported by APIs */
protected function verbs(){
return [
'create' => ['POST'],
'update' => ['PUT', 'PATCH','POST'],
'delete' => ['DELETE'],
'view' => ['GET'],
'index'=>['GET'],
];
}
public function actionCreate(){
Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
$model = new Cab;
$post_data = Yii::$app->request->post();
$model->load($post_data);
$model->save(false);
return $model;
}
Include safe columns in your model
public function rules()
{
return [
[['sum', 'currency'], 'safe'],
];
}
I am trying to call model methods from controller. but I am getting Fatal error: Class 'GuestModel' not found in. error
following is the code ::
Controller ::
class GuestController extends Zend_Controller_Action
{
public function indexAction(){
$guestbook = new GuestModel();
$this->view->entries = $guestbook->fetchAll();
}
}
Model::
class GuestModel extends Zend_Db_Table_Abstract
{
public function fetchAll()
{
$resultSet = $this->getDbTable()->fetchAll();
$entries = array();
foreach ($resultSet as $row) {
$entry = new Application_Model_Guestbook();
$entry->setId($row->id)
->setEmail($row->email)
->setComment($row->comment)
->setCreated($row->created);
$entries[] = $entry;
}
return $entries;
}
public function getDbTable()
{
if (null === $this->_dbTable) {
$this->setDbTable('Application_Model_DbTable_Guestbook');
}
return $this->_dbTable;
}
public function setDbTable($dbTable)
{
if (is_string($dbTable)) {
$dbTable = new $dbTable();
}
if (!$dbTable instanceof Zend_Db_Table_Abstract) {
throw new Exception('Invalid table data gateway provided');
}
$this->_dbTable = $dbTable;
return $this;
}
}
Zend Framework autoload depends on using the correct directory structure and file naming conventions to find the classes automagically, from the looks of your code my guess would be you're not following it.
I see 2 possible solutions for your problem:
If possible, rename your class to Application_Model_Guestbook, the file to Guestbook.php and make sure to move it to your application/models/ directory. Then you just need to call it in your controller as $guestbook = new Application_Model_Guestbook();. Check this documentation example;
Create your own additional autoloading rules. Check the official documentation regarding Resource Autoloading.