I am having some trouble getting the Yii2 RESTful API returning relational data. I have this working when viewed through the frontend but i am trying to get the same data through the API and its not working the same way.
Tables
country - PK is population_id
population - Foreign Key is country.population_id
I am getting this error:
{ "success": false, "data": { "name": "Invalid Configuration",
"message": "The \"query\" property must be an instance of a class that
implements the QueryInterface e.g. yii\db\Query or its subclasses.",
"code": 0, "type": "yii\base\InvalidConfigException", "file":
"C:\xampp\htdocs\AdvancedAPI\vendor\yiisoft\yii2\data\ActiveDataProvider.php",
"line": 100, "stack-trace": [ "#0
C:\xampp\htdocs\AdvancedAPI\vendor\yiisoft\yii2\data\BaseDataProvider.php(79):
yii\data\ActiveDataProvider->prepareModels()", "#1
C:\xampp\htdocs\AdvancedAPI\vendor\yiisoft\yii2\data\BaseDataProvider.php(92):
yii\data\BaseDataProvider->prepare()", "#2
C:\xampp\htdocs\AdvancedAPI\vendor\yiisoft\yii2\rest\Serializer.php(162):
yii\data\BaseDataProvider->getModels()", "#3
C:\xampp\htdocs\AdvancedAPI\vendor\yiisoft\yii2\rest\Serializer.php(131):
yii\rest\Serializer->serializeDataProvider(Object(yii\data\ActiveDataProvider))",
"#4
C:\xampp\htdocs\AdvancedAPI\vendor\yiisoft\yii2\rest\Controller.php(97):
yii\rest\Serializer->serialize(Object(yii\data\ActiveDataProvider))",
"#5
C:\xampp\htdocs\AdvancedAPI\vendor\yiisoft\yii2\rest\Controller.php(75):
yii\rest\Controller->serializeData(Object(yii\data\ActiveDataProvider))",
"#6
C:\xampp\htdocs\AdvancedAPI\vendor\yiisoft\yii2\base\Controller.php(153):
yii\rest\Controller->afterAction(Object(yii\base\InlineAction),
Object(yii\data\ActiveDataProvider))", "#7
C:\xampp\htdocs\AdvancedAPI\vendor\yiisoft\yii2\base\Module.php(455):
yii\base\Controller->runAction('index', Array)", "#8
C:\xampp\htdocs\AdvancedAPI\vendor\yiisoft\yii2\web\Application.php(83):
yii\base\Module->runAction('v1/country/inde...', Array)", "#9
C:\xampp\htdocs\AdvancedAPI\vendor\yiisoft\yii2\base\Application.php(375):
yii\web\Application->handleRequest(Object(yii\web\Request))", "#10
C:\xampp\htdocs\AdvancedAPI\api\web\index.php(17):
yii\base\Application->run()", "#11 {main}" ] } }
model (Country.php):
<?php
namespace api\modules\v1\models;
use \yii\db\ActiveRecord;
class Country extends ActiveRecord
{
public static function tableName()
{
return 'country';
}
public function getCountries()
{
//return $this->hasMany(Population::className(), ['population_id' => 'population_id']);
return $this->hasMany(Country::className(), ['population_id' => 'population_id']);
}
public function getPopulationNumber()
{
//return $this->hasOne(Country::className(), ['population_id' => 'population_id']);
return $this->hasOne(Population::className(), ['population_id' => 'population_id']);
}
}
model (Population.php):
<?php
namespace api\modules\v1\models;
use \yii\db\ActiveRecord;
class Population extends ActiveRecord
{
/**
* #inheritdoc
*/
public static function tableName()
{
return 'population';
}
/**
* #inheritdoc
*/
public static function primaryKey()
{
return ['p_id'];
}
}
controller (CountryController.php):
<?php
namespace api\modules\v1\controllers;
use yii\rest\Controller;
use yii\data\ActiveDataProvider;
use api\modules\v1\models\Country;
class CountryController extends Controller
{
public function actionIndex()
{
$query = Country::find()->with('countries', 'populationNumber')->all();
//$query = Country::find();
return new ActiveDataProvider([
'query' => $query,
]);
}
}
You need to remove all() part from your query. So the code should be:
<?php
namespace api\modules\v1\controllers;
use yii\rest\Controller;
use yii\data\ActiveDataProvider;
use api\modules\v1\models\Country;
class CountryController extends Controller
{
public function actionIndex()
{
$query = Country::find()->with('countries', 'populationNumber');
//$query = Country::find();
return new ActiveDataProvider([
'query' => $query,
]);
}
}
Related
I'm working on a mobile app that has to show some images of houses from a server. I have installed Symfony2, FOSRestBundle and Sonata Media Bundle for the backend.
In order to get the houses images URLs, I have configured FOSRestBundle for an entity named Property which has a gallery field. This is the REST controller
class PropertiesController extends FOSRestController
{
public function getPropertiesAction()
{
$response = $this->getDoctrine()->getRepository('ComissionBundle:Property')->findAll();
if ($response == null){
return "No properties";
}
else{
return $response;
}
}
}
But i get this:
[
{
"id":2,
"name":"test",
"city":"test",
"address":"test",
"sector":"test",
"area":0,
"rooms":112343,
"price":0,
"gallery":{
"context":"default",
"name":"test",
"enabled":false,
"updated_at":"2016-08-26T17:18:51+0200",
"created_at":"2016-08-26T17:18:51+0200",
"default_format":"default_small",
"id":1
}
}
]
As you can see, there are no media objects.
Then, I tried with a customized repository method:
class PropertyRepository extends EntityRepository
{
public function findMainInfoElements($elements)
{
$em = $this->getEntityManager();
$queryText = "SELECT u, g, h FROM ComissionBundle:Property u JOIN u.gallery g JOIN g.galleryHasMedias h";
$query = $em->createQuery($queryText);
return $query->setMaxResults($elements)->getResult();
}
}
but the result is the same.
How can i get the URLs from the gallery in order to show them in the mobile app? (Especially the thumb images that Sonata Media Bundle generates, which are better for the app performance)
I guess you should join the galleryHasMedia entity with the Media entity too in the PropertyRepository query, but it will not be enough.
You should create a custom serialization handler for the Media entity too.
Declare the serialization handler
myapp.serialization.media_handler:
class: Path\ToYourApp\Serializer\MediaSerializationHandler
tags:
- { name: jms_serializer.handler, type: Path\ToYourMedia\Entity\Media, direction: serialization, format: json, method: serializeMedia }
arguments: [ #sonata.media.provider.image, #sonata.media.provider.file ]
Create the serialization handler
<?php
namespace Camelot\Social\ApiBundle\Serializer;
use Path\ToYourMedia\Entity\Media;
use JMS\Serializer\JsonSerializationVisitor;
use JMS\Serializer\Context;
use Sonata\MediaBundle\Provider\ImageProvider;
use Sonata\MediaBundle\Provider\FileProvider;
class MediaSerializationHandler
{
/**
* #var ImageProvider
*/
private $imageProvider;
/**
* #var FileProvider
*/
private $fileProvider;
public function __construct(ImageProvider $imageProvider, FileProvider $fileProvider)
{
$this->imageProvider = $imageProvider;
$this->fileProvider = $fileProvider;
}
public function serializeMedia(JsonSerializationVisitor $visitor, Media $media, array $type, Context $context)
{
switch ($media->getProviderName()) {
case 'sonata.media.provider.file':
$serialization = $this->serializeFile($media);
break;
case 'sonata.media.provider.image':
$serialization = $this->serializeImage($media);
break;
default:
throw new \RuntimeException("Serialization media provider not recognized");
}
if ($visitor->getRoot() === null) {
$visitor->setRoot($serialization);
}
return $serialization;
}
private function serializeImage(Media $media)
{
// here you can provide one ore more URLs based on your SonataMedia configuration
// you can also add some more properties coming from the media entity based on your needs (e.g. authorName, description, copyright etc)
return [
"url" => [
"orig" => $this->imageProvider->generatePublicUrl($media, "reference"),
"small" => $this->imageProvider->generatePublicUrl($media, "default_small"),
"big" => $this->imageProvider->generatePublicUrl($media, "default_big"),
]
];
}
private function serializeFile(Media $media)
{
return [
"name" => $media->getName(),
"size" => $media->getSize(),
"url" => $this->fileProvider->generatePublicUrl($media, 'reference')
];
}
}
I'm doing a query on a really simple table in a typo 3 task. However, only the fields "uid" and "pid" are returned, the other fields are NULL.
My Entity:
<?php
namespace Name\SampleExtension\Domain\Model;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
class MailAgent extends AbstractEntity
{
/**
* #var integer
*/
protected $uid;
/**
* #var string
*/
protected $customeremail;
/**
* #var string
*/
protected $searchparameters;
/**
* #var string
*/
protected $resultlist;
public function getUid()
{
return $this->uid;
}
public function setCustomerEmail($customeremail)
{
$this->customeremail = $customeremail;
}
public function getCustomerEmail()
{
return $this->customeremail;
}
public function setSearchParameters($searchparameters)
{
$this->searchparameters = $searchparameters;
}
public function getSearchParameters()
{
return $this->searchparameters;
}
public function setResultList($resultlist)
{
$this->resultlist = $resultlist;
}
public function getResultList()
{
return $this->resultlist;
}
}
?>
The Repository:
<?php
namespace Name\SampleExtension\Domain\Repository;
use TYPO3\CMS\Extbase\Persistence\Repository;
class MailAgentRepository extends Repository
{
public function findByUids($uids)
{
$query = $this->createQuery();
foreach ($uids as $uid) {
$constraints[] = $query->equals('uid', $uid);
}
return $query->matching(
$query->logicalOr(
$constraints
)
)->execute();
}
}
?>
And the query inside the task:
<?php
namespace Name\SampleExtension\Task;
use TYPO3\CMS\Scheduler\Task\AbstractTask;
use TYPO3\CMS\Extbase\Object\ObjectManager;
use TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager;
use Name\SampleExtension\Domain\Model\MailAgent;
use Name\SampleExtension\Domain\Repository\MailAgentRepository;
class MailAgentCheckup extends AbstractTask
{
public function execute() {
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
$this->MailAgentRepository = $objectManager->get(MailAgentRepository::class);
$query = $this->MailAgentRepository->createQuery();
$allCustomers = $this->MailAgentRepository->findAll();
foreach ($allCustomers as $customer) {
var_dump($customer);
}
return true;
}
}
?>
I have no idea why the other fields are not returned, but the uid and the pid are. My guess is that I need to declare the mapping somewhere else.
EDIT: Heres the content of my TCA, which is probably wrong or not enough, but since I'm working on a existing extension I was copying from the TCA's of the tables that work.
tx_sampleextension_domain_model_mailagent.php
return [
'columns' => [
'uid' => [],
'customer_email' => [],
'search_parameters' => [],
'result_list' => [],
],
'types' => [],
];
This is from another table for which querys etc work
return [
'columns' => [
'immovable' => [],
'type' => [],
'title' => [],
'path' => [],
'mark_to_delete' => [],
],
];
Give a try to inject your repository
<?php
namespace Name\SampleExtension\Task;
use TYPO3\CMS\Scheduler\Task\AbstractTask;
use TYPO3\CMS\Extbase\Object\ObjectManager;
use TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager;
use Name\SampleExtension\Domain\Model\MailAgent;
use Name\SampleExtension\Domain\Repository\MailAgentRepository;
use TYPO3\CMS\Extbase\Utility\DebuggerUtility;
class MailAgentCheckup extends AbstractTask
{
/**
* mailAgentRepository
*
* #var \Name\SampleExtension\Domain\Repository\MailAgentRepository
* #inject
*/
protected $mailAgentRepository = NULL;
public function injectMailAgentRepository(\Name\SampleExtension\Domain\Repository\MailAgentRepository $mailAgentRepository) {
$this->mailAgentRepository = $mailAgentRepository;
}
public function execute() {
$allCustomers = $this->mailAgentRepository->findAll();
DebuggerUtility::var_dump($allCustomers);exit;
// OR
$arguments = $this->request->getArguments();
$uid = $arguments['uid'];
$singleCustomer = $this->mailAgentRepository->findByUid(intval($uid));
DebuggerUtility::var_dump($singleCustomer);exit;
/*foreach ($allCustomers as $customer) {
var_dump($customer);
}*/
return true;
}
}
?>
I was missing the TCA file for the table. After adding it and declaring all the columns in there, the extbase domain object variables got filled.
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'],
];
}
When adding an entry through the rest api how to change a variable before save values?
Below is a part of the controller code:
class RestusersController extends ActiveController
{
public $modelClass = 'app\models\User';
public function actions()
{
$actions = parent::actions();
$actions['index']['prepareDataProvider'] = [$this, 'prepareDataProvider'];
return $actions;
}
public function prepareDataProvider()
{
return new ActiveDataProvider([
'query' => User::find()->where(['status_id'=>'1']),
'pagination' => false,
]);
}
}
eg change the variable $ this-> status_id = 1;
public function beforeSave($insert)
{
$this->status_id = 1;
return parent::beforeSave($insert);
}
I used Zend Framework 2.1(not 2.0x) method to populate a select/drop down that is described in following links:
http://zf2.readthedocs.org/en/develop/modules/zend.form.advanced-use-of-forms.html#handling-dependencies
http://www.michaelgallego.fr/blog/2012/11/09/discover-whats-coming-for-zendform-in-zf-2-1/
Though it seems I have done as they told I got a error message like:
*... ::__construct() must be an instance of Zend\Db\TableGateway\TableGateway, none given, called in ...*
which seems service locator is not used properly.
My form code that adds my FieldSet SupplierFieldset:
namespace Inventory\Form;
use Zend\Form\Form;
use Inventory\Model;
class ItemForm extends Form
{
public function init()
{
$this->add(array(
'name' => 'sup_code',
'type' => 'Inventory\Form\SupplierFieldset'
));
}
}
My 'SupplierFieldset' class:
namespace Inventory\Form;
use Inventory\Model;
use Zend\Form\Element;
use Zend\Form\Fieldset;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\InputFilter\InputFilterProviderInterface;
use Inventory\Model\SupplierTable;
use Inventory\Model\Supplier;
class SupplierFieldset extends Fieldset implements ServiceLocatorAwareInterface
{
protected $serviceLocator;
protected $supplierTable;
public function init()
{
parent::__construct('Suppliers Code');
$this->setLabel('Supplier Code');
$this->setName('supplier_code');
$suppliers = $this->getSupplierTable()->fetchAll();
$select = new Element\Select('supplier_code');
$options = array();
foreach ($suppliers as $supplier) {
$options[$supplier->id] = $supplier->sup_code;
}
$select->setValueOptions($options);
}
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
}
public function getServiceLocator()
{
return $this->serviceLocator;
}
public function getSupplierTable()
{
if (!$this->supplierTable) {
$sm = $this->getServiceLocator();
$this->supplierTable = $sm->get('Inventory\Model\SupplierTable');
}
return $this->supplierTable;
}
}
My Module.php getFormElementConfig() function:
public function getFormElementConfig()
{
return array(
'factories' => array(
'SupplierFieldset' => function($sm) {
$serviceLocator = $sm->getServiceLocator();
$supplierTable = $serviceLocator->get('Inventory\Model\SupplierTable');
$fieldset = new SupplierFieldset($supplierTable);
return $fieldset;
}
)
);
}
My SupplierTable.php model:
namespace Inventory\Model;
use Zend\Db\TableGateway\TableGateway;
class SupplierTable
{
protected $tableGateway;
public function __construct(TableGateway $tableGateway)
{
$this->tableGateway = $tableGateway;
}
public function fetchAll()
{
$resultSet = $this->tableGateway->select();
return $resultSet;
}
}
I know SupplierTable model's constructor needs a TableGateway $tableGateway parameter. But this model is working properly when called from SupplierController.