So far, I have figured out how to return a typical JSON response in Zend Framework 2. First, I added the ViewJsonStrategy to the strategies section of the view_manager configuration. Then, instead of returning a ViewModel instance from the controller action, I return a JsonModel instance with all my variables set.
Now that I've figured that piece out, I need to understand how to render a view and return it within that JSON response. In ZF1, I was able to use $this->view->render($scriptName), which returned the HTML as a string. In ZF2, the Zend\View\View::render(...) method returns void.
So... how can I render an HTML view script and return it in a JSON response in one request?
This is what I have right now:
if ($this->getRequest()->isXmlHttpRequest()) {
$jsonModel = new JsonModel(...);
/* #todo Render HTML script into `$html` variable, and add to `JsonModel` */
return $jsonModel;
} else {
return new ViewModel(...);
}
OK, i think i finally understood what you're doing. I've found a solution that i think matches your criteria. Though i am sure that there is room for improvement, as there's some nasty handwork to be done...
public function indexAction()
{
if (!$this->getRequest()->isXmlHttpRequest()) {
return array();
}
$htmlViewPart = new ViewModel();
$htmlViewPart->setTerminal(true)
->setTemplate('module/controller/action')
->setVariables(array(
'key' => 'value'
));
$htmlOutput = $this->getServiceLocator()
->get('viewrenderer')
->render($htmlViewPart);
$jsonModel = new JsonModel();
$jsonModel->setVariables(array(
'html' => $htmlOutput,
'jsonVar1' => 'jsonVal2',
'jsonArray' => array(1,2,3,4,5,6)
));
return $jsonModel;
}
As you can see, the templateMap i create is ... nasty ... it's annoying and i'm sure it can be improved by quite a bit. It's a working solution but just not a clean one. Maybe somehow one would be able to grab the, probably already instantiated, default PhpRenderer from the ServiceLocator with it's template- and path-mapping and then it should be cleaner.
Thanks to the comment ot #DrBeza the work needed to be done could be reduced by a fair amount. Now, as I'd initially wanted, we will grab the viewrenderer with all the template mapping intact and simply render the ViewModel directly. The only important factor is that you need to specify the fully qualified template to render (e.g.: "$module/$controller/$action")
I hope this will get you started though ;)
PS: Response looks like this:
Object:
html: "<h1>Hello World</h1>"
jsonArray: Array[6]
jsonVar1: "jsonVal2"
You can use more easy way to render view for your JSON response.
public function indexAction() {
$partial = $this->getServiceLocator()->get('viewhelpermanager')->get('partial');
$data = array(
'html' => $partial('MyModule/MyPartView.phtml', array("key" => "value")),
'jsonVar1' => 'jsonVal2',
'jsonArray' => array(1, 2, 3, 4, 5, 6));
$isAjax = $this->getRequest()->isXmlHttpRequest());
return isAjax?new JsonModel($data):new ViewModel($data);
}
Please note before use JsonModel class you need to config View Manager in module.config.php file of your module.
'view_manager' => array(
.................
'strategies' => array(
'ViewJsonStrategy',
),
.................
),
it is work for me and hope it help you.
In ZF 3 you can achieve the same result with this code
MyControllerFactory.php
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$renderer = $container->get('ViewRenderer');
return new MyController(
$renderer
);
}
MyController.php
private $renderer;
public function __construct($renderer) {
$this->renderer = $renderer;
}
public function indexAction() {
$htmlViewPart = new ViewModel();
$htmlViewPart
->setTerminal(true)
->setTemplate('module/controller/action')
->setVariables(array('key' => 'value'));
$htmlOutput = $this->renderer->render($htmlViewPart);
$json = \Zend\Json\Json::encode(
array(
'html' => $htmlOutput,
'jsonVar1' => 'jsonVal2',
'jsonArray' => array(1, 2, 3, 4, 5, 6)
)
);
$response = $this->getResponse();
$response->setContent($json);
$response->getHeaders()->addHeaders(array(
'Content-Type' => 'application/json',
));
return $this->response;
}
As usual framework developer mess thing about AJAX following the rule why simple if might be complex Here is simple solution
in controller script
public function checkloginAction()
{
// some hosts need to this some not
//header ("Content-type: application/json"); // this work
// prepare json aray ....
$arr = $array("some" => .....);
echo json_encode($arr); // this works
exit;
}
This works in ZF1 and ZF2 as well
No need of view scrpt at all
If you use advise of ZF2 creator
use Zend\View\Model\JsonModel;
....
$result = new JsonModel($arr);
return $result;
AJAX got null as response at least in zf 2.0.0
Related
I'm new at phpspec (coming from phpunit) and I have problems setting the behavior of a mock returned by another mock.
I'm creating a wrapper class around the Guzzle client and I want to check the output of the response.
Here's the spec:
function it_gets_response_status_code(Client $client, Url $url, Response $response)
{
$this->beConstructedWith($client);
$url->__toString()->willReturn('http://example.com');
$data = ['foo' => 'bar'];
$response->getStatusCode()->willReturn(200);
$client->request('POST', $url, ['form_params' => $data])->willReturn($response);
$this->post($url, $data);
assert($this->getResponseStatusCode() === 200); // Failing! :(
}
and the corresponding functions in my class:
public function post(Url $url, array $data)
{
$this->response = $this->client->request('POST', (string) $url, ['form_params' => $data]);
}
public function getResponseStatusCode()
{
return $this->response->getStatusCode();
}
The assertion is failing and when I check what is this status code, I see that instead of the integer 200, it's an instance of PhpSpec\Wrapper\Subject. What am I missing here?
I've searched and googled but cannot find resources about using the mock returned by another mock in phpspec. I'm wondering if the reason for this is that it's a code smell? If so I'd be glad to see how I could do this differently (currently I cannot see how I could keep the code simple and doing differently).
try:
assert($this->getResponseStatusCode()->getWrappedObject() === 200);
this:
$response->getStatusCode()->willReturn(200)
returns a '200' string wrapped in an Subject object, on which you can then make mock/stub calls if needed. To get the real value of the subject you need to call getWrappedObject
i'm trying to queue an email sending invoice emails in laravel 5.1, i pass in a variable called invoice, when i dd($invoice->dateString()) in the Job class it's return the correct value but when i pass it in to the view the $invoice variable return empty array (so i get an error about trying to get property from non-object...).
the second problem i have is when i try to add attachment to the job it returns an error : "Serialization of closure failed: Serialization of 'SplFileInfo' is not allowed".
the job class looks like that:
namespace LM2\Jobs;
use Guzzle\Service\Client;
use LM2\Jobs\Job;
use Illuminate\Contracts\Bus\SelfHandling;
use Illuminate\Contracts\Queue\ShouldQueue;
use LM2\Models\User as User;
use LM2\Models\Client as LMClient;
class SendInvoiceEmail extends Job implements SelfHandling, ShouldQueue
{
protected $user;
protected $invoice;
protected $attachment;
protected $update;
public function __construct(User $user, LMClient $client, $invoice,$update)
{
$this->user = $user;
$this->client = $client;
$this->invoice = $invoice;
$this->update = $update;
}
public function handle()
{
$attachment = $this->client->invoiceFile($this->invoice->id,['vendor' => 'Test','product' => 'Your Product']);
$invoice = $this->invoice;
$data = [
'invoice' => $this->invoice,
'update'=> $this->update,
];
$user = $this->user;
\Mail::queue('emails.invoices', $data , function($m) use ($user,$invoice,$attachment){
$m->to($user->email)->subject('New payment received')->attach($attachment);
});
}
}
and my controller function looks like that:
public function sendEmailInvoice($update = false){
$client = \Auth::client();
$user = \Auth::user();
$invoices = $client->invoices();
$this->dispatch(new SendInvoiceEmail($user,$client,$invoices[0],$update));
$activity = $data['update'] ? 'updated': 'added';
return ['success', $activity];
}
can someone please tell me what am i doing wrong?
thanks a lot you all :)
Just a guess... but when using Mail::queue() the $data get's converted/cast to an array/you lose your objects inside of the view - hence why you're receiving errors when trying to call methods(), because they don't exist.
Rather than passing invoice + update objects, get what you need from them in the handle method and construct the $data array.
$data = [
'invoice_foo' => $invoice->getFoo(),
'invoice_bar' => $invoice->getBar(),
];
*** Apologies if this doesn't help at all!
so i found the answer thanks to #Michael, i have changed my handle so it's look like this now:
public function handle(Mailer $mailer)
{
$client = $this->client;
$invoice = $this->invoice;
$data = [
'date' => $invoice->dateString(),
'amount' => $invoice->dollars(),
'update'=> $this->update,
];
$user = $this->user;
return $mailer->queue('emails.invoices', $data , function($m) use ($user,$client,$invoice){
$attachment = $client->invoiceFile($invoice->id,['vendor' => 'Infogamy','product' => 'Your Product']);
$m->to($user->email)->subject('New payment received')->attach($attachment);
});
}
The attachment should be processed inside the mailer callback function, and the function called from the $invoice variable (object) should be called inside the handle function and not in the blade view template.
so, using urlManager i've been able to get to this point:
'people/<id:\d+>/<last:.*?>/<name:.*?>'=>'person/view',
which generates this: http://foo.com/people/3/smith/john
I want to eliminate the ID number, which in this case is "3"
I tried using this:
'people/<last:.*?>/<name:.*?>'=>'person/view', //makes sense right?
however, when i try to navigate to http://foo.com/people/smith/john , I get a server 400 error.
What gives? Should I try modifying the .htaccess file instead?
As requested here is my entire urlManager component:
'urlManager'=>array(
'urlFormat'=>'path',
'showScriptName'=>false,
'rules'=>array(
'about' => array('site/page', 'defaultParams' => array('view' => 'about')),
'contact'=>'site/contact',
'login' =>'site/login',
'/'=>'site/index',
'people/<last:.*?>/<name:.*?>'=>'person/view',
'<controller:\w+>/<action:\w+>/<id:\d+>'=>'<controller>/<action>',
'<controller:\w+>/<action:\w+>'=>'<controller>/<action>',
),
),
Here is my PersonController.php file:
public function actionView()
{
$person=$this->loadModel();
$this->render('view',array(
'model'=>$person,
));
}
private $_model;
public function loadModel()
{
if($this->_model===null)
{
if(isset($_GET['last']))
{
$this->_model=Person::model()->findByPk($_GET['id']);
}
if($this->_model===null)
throw new CHttpException(404,'The requested page does not exist.');
}
return $this->_model;
}
Your view action needs to accept the names and do a find by the attributes instead of using loadModel, which requires the ID:
public function actionView($last, $name)
{
$person = Person::model()->find(
'name=":name" AND last=":last"',
array(':name'=>$name, ':last'=>$last)
);
if($person === null) throw new CHttpException(404);
$this->render('view',array(
'model'=>$person,
));
}
Do note that your request will not support multiple people with same first and last names!
You probably need a rewrite condition for URLs so requests are still routed back to index.php.
check out this guide: http://www.yiiframework.com/doc/guide/1.1/en/topics.url#user-friendly-urls
look at the actionView of the PersonController
public function actionView($id,...)
{
}
So the view action must need an $id parameter for it to work. Otherwise it throws a 400 bad request error. In your modified url rule the only parameters specified are $last and $name. The solution is to remove the $id parameter to actionView() if it is not used inside actionView. Otherwise you have to change function logic for determining the required person from $last and $name instead of $id
I have the following code blocks:
class MerchantStoreForm extends sfForm
{
public function configure()
{
$this->disableCSRFProtection();
$this->setWidgets(array(
'brand_id' => new sfWidgetFormDoctrineChoice(array('label'=> 'Store Brand','model'=>'Brand','add_empty'=>'-Select Brand-','method'=>'getName','key_method'=>'getId','order_by'=>array('name','asc'))),
'newbrand' => new sfWidgetFormInputCheckbox(array('label' => 'New'),array('value'=>'Y'))
));
$this->setValidators(array(
'newbrand' => new sfValidatorString(array('required'=>false)),
'brand_id' => new sfValidatorDoctrineChoice(array('model'=>'Brand'))
));
$brand = new Brand();
$brand_form = new BrandForm();
$brand_form->widgetSchema['name']->setAttribute('style','display:none');
$this->embedForm('brand', $brand_form);
$this->getWidgetSchema()->setNameFormat('store[%s]');
}
public function execute()
{
$form_values = $this->getValues();
if($form_values['newbrand'])
{
$brand_form = $this->getEmbeddedForm('brand');
$brand_form->save();
$brand = $brand_form->getObject();
}
else
{
$brand = doctrine::getTable('Brand')->findOneById($form_values['brand_id']);
}
return $brand->getId();
}
}
Two questions:
1) The magic of $brand_form->save() doesn't work for me. I get a 500 Internal Server Error sfValidatorErrorSchema error pointing to the following piece of code in my symfony generated BaseBrandForm.class.php:
...
$this->widgetSchema->setNameFormat('brand[%s]');
$this->errorSchema = new sfValidatorErrorSchema($this->validatorSchema);
...
This works instead in replacement though:
$brand_form->updateObject($form_values['brand']);
$brand_form->getObject()->save();
Why is this?
2) Why do I get an undefined method error while calling getter method on the object of the BaseFormDoctrine embedded form:
return $brand->getId();
Thanks in advance for your help.
Sharmil
1) BrandForm throws an exception because it doesn't have any values. Classes that extend sfFormObject don't play nicely when embedded directly into non object forms (like sfForm).
What is MerchantStoreForm doing? Depending on the situation, it should probably be extending sfFormObject or BrandForm should be the top level form. If this isn't possible, you'll have to write add a save method to MerchantStoreForm that calls updateObject and save. To better understand what's happening, go through the logic that takes place in sfFormObject - it's worth knowing especially if you're using embedded forms.
2) No clue here. I would see what $brand is actually an instance of. If it's a record and that record has an id field, there's no reason that shouldn't work.
In symfony, I call an action and I want this to return json to jQuery frontend.
The Jobeet tutorial teaches how to return a partial but I want to return json, not a partial.
If it's just a normal AJAX action you're returning it from, I think I've used the following somewhere in the past:
return $this->renderText(json_encode($something));
The cheap way:
function executeSomethingThatReturnsJson(){
$M = new Model();
$stuff = $M->getStuff();
echo json_encode($stuff);
die(); //don't do any view stuff
}
The smarter way:
A smarter way is to create a nice subclass of sfActions that helps handling json-stuff.
In a project I did recently, I created a application called 'api' (./symfony generate:application api)
and then created a file like:
api/lib/apiActions.class.php
<?PHP
class apiActions extends sfActions {
public function returnJson($data){
$this->data = $data;
if (sfConfig::get('sf_environment') == 'dev' && !$this->getRequest()->isXmlHttpRequest()){
$this->setLayout('json_debug');
$this->setTemplate('json_debug','main');
}else{
$this->getResponse()->setHttpHeader('Content-type','application/json');
$this->setLayout('json');
$this->setTemplate('json','main');
}
}
}
Notice that I explicitly set the template there.
So my jsonSuccess.php template is simply:
<?PHP echo json_encode($data);
While json_debugSuccess.php makes things prettier:
<?PHP var_dump($data); ?>
Then you can have a controller that extends apiActions (instead of the usual sfActions) that looks like this:
<?php
class myActions extends apiAction {
public function executeList(sfWebRequest $request)
{
$params = array();
if ($request->hasParameter('id')){
$id = $request->getParameter('id');
if (is_numeric($id)){
$params['id'] = $id;
}
}
$data = Doctrine::getTable('SomeTable')->findAll();
$this->returnJson($data);
}
}
Disclaimer: The code above is copy/pasted out of an app I have, but simplified. It's for illustrative purposes only -- but it should get you heading in the right direction.
FYI: In case of Symfony 2.x "quick and dirty" way looks like this:
return new Response(json_encode($data), 200, array('Content-Type', 'text/json'));
Return new JsonResponse(array);