Cakephp3: How can I return json data? - json

I am having a ajax post call to a cakePhp Controller:
$.ajax({
type: "POST",
url: 'locations/add',
data: {
abbreviation: $(jqInputs[0]).val(),
description: $(jqInputs[1]).val()
},
success: function (response) {
if(response.status === "success") {
// do something with response.message or whatever other data on success
console.log('success');
} else if(response.status === "error") {
// do something with response.message or whatever other data on error
console.log('error');
}
}
});
When I try this I get the following error message:
Controller actions can only return Cake\Network\Response or null.
Within the AppController I have this
$this->loadComponent('RequestHandler');
enabled.
the Controller function looks like this:
public function add()
{
$this->autoRender = false; // avoid to render view
$location = $this->Locations->newEntity();
if ($this->request->is('post')) {
$location = $this->Locations->patchEntity($location, $this->request->data);
if ($this->Locations->save($location)) {
//$this->Flash->success(__('The location has been saved.'));
//return $this->redirect(['action' => 'index']);
return json_encode(array('result' => 'success'));
} else {
//$this->Flash->error(__('The location could not be saved. Please, try again.'));
return json_encode(array('result' => 'error'));
}
}
$this->set(compact('location'));
$this->set('_serialize', ['location']);
}
What do I miss here? Is there any additional settings needed?

Instead of returning the json_encode result, set the response body with that result and return it back.
public function add()
{
$this->autoRender = false; // avoid to render view
$location = $this->Locations->newEntity();
if ($this->request->is('post')) {
$location = $this->Locations->patchEntity($location, $this->request->data);
if ($this->Locations->save($location)) {
//$this->Flash->success(__('The location has been saved.'));
//return $this->redirect(['action' => 'index']);
$resultJ = json_encode(array('result' => 'success'));
$this->response->type('json');
$this->response->body($resultJ);
return $this->response;
} else {
//$this->Flash->error(__('The location could not be saved. Please, try again.'));
$resultJ = json_encode(array('result' => 'error', 'errors' => $location->errors()));
$this->response->type('json');
$this->response->body($resultJ);
return $this->response;
}
}
$this->set(compact('location'));
$this->set('_serialize', ['location']);
}
Edit (credit to #Warren Sergent)
Since CakePHP 3.4, we should use
return $this->response->withType("application/json")->withStringBody(json_encode($result));
Instead of :
$this->response->type('json');
$this->response->body($resultJ);
return $this->response;
CakePHP Documentation

Most answers I've seen here are either outdated, overloaded with unnecessary information, or rely on withBody(), which feels workaround-ish and not a CakePHP way.
Here's what worked for me instead:
$my_results = ['foo'=>'bar'];
$this->set([
'my_response' => $my_results,
'_serialize' => 'my_response',
]);
$this->RequestHandler->renderAs($this, 'json');
More info on RequestHandler. Seemingly it's not getting deprecated anytime soon.
UPDATE: CakePHP 4
$this->set(['my_response' => $my_results]);
$this->viewBuilder()->setOption('serialize', true);
$this->RequestHandler->renderAs($this, 'json');
More info

there are few things to return JSON response:
load RequestHandler component
set rendering mode as json
set content type
set required data
define _serialize value
for example you can move first 3 steps to some method in parent controller class:
protected function setJsonResponse(){
$this->loadComponent('RequestHandler');
$this->RequestHandler->renderAs($this, 'json');
$this->response->type('application/json');
}
later in your controller you should call that method, and set required data;
if ($this->request->is('post')) {
$location = $this->Locations->patchEntity($location, $this->request->data);
$success = $this->Locations->save($location);
$result = [ 'result' => $success ? 'success' : 'error' ];
$this->setJsonResponse();
$this->set(['result' => $result, '_serialize' => 'result']);
}
also it looks like you should also check for request->is('ajax); I'm not sure about returning json in case of GET request, so setJsonResponse method is called within if-post block;
in your ajax-call success handler you should check result field value:
success: function (response) {
if(response.result == "success") {
console.log('success');
}
else if(response.result === "error") {
console.log('error');
}
}

In the latest version of CakePHP $this->response->type() and $this->response->body() are deprecated.
Instead you should use $this->response->withType() and $this->response->withStringBody()
E.g:
(this was pinched from the accepted answer)
if ($this->request->is('post')) {
$location = $this->Locations->patchEntity($location, $this->request->data);
if ($this->Locations->save($location)) {
//$this->Flash->success(__('The location has been saved.'));
//return $this->redirect(['action' => 'index']);
$resultJ = json_encode(array('result' => 'success'));
$this->response = $this->response
->withType('application/json') // Here
->withStringBody($resultJ) // and here
return $this->response;
}
}
Relevant Documentation

When you return JSON data you need to define the data type and response body information like below:
$cardInformation = json_encode($cardData);
$this->response->type('json');
$this->response->body($cardInformation);
return $this->response;
In you case just change this return json_encode(array('result' => 'success')); line with below code:
$responseResult = json_encode(array('result' => 'success'));
$this->response->type('json');
$this->response->body($responseResult);
return $this->response;

RequestHandler is not required to send json.
In controller's action:
$this->viewBuilder()->setClassName('Json');
$result = ['result' => $success ? 'success' : 'error'];
$this->set($result);
$this->set('_serialize', array_keys($result));

As of cakePHP 4.x.x the following should work assuming that your controller and routes are set as shown below:
controller: <your_project_name>/src/Controller/StudentsController.php
public function index()
{
$students = $this->Students->find('all');
$this->set(compact('students'));
$this->viewBuilder()->setOption('serialize',['students']);
}
Routes: <your_project_name>/config/routes.php
<?php
use Cake\Routing\Route\DashedRoute;
use Cake\Routing\RouteBuilder;
/** #var \Cake\Routing\RouteBuilder $routes */
$routes->setRouteClass(DashedRoute::class);
$routes->scope('/', function (RouteBuilder $builder) {
$builder->setExtensions(['json']);
$builder->resources('Students');
$builder->fallbacks();
});
Run bin/cake server and visit http://localhost:8765/students.json using postman/insomnia or just the normal browser.
See further documentation for setting up Restful controllers and Restful Routing
Don't forget to set the method to GET on postman and insomnia.

Though I'm not a CakePHP Guru, in my case i'm using cake > 4 and I need some results by ajax call. For this, from my controller i wrote,
echo json_encode(Dashboard::recentDealers()); die;
and in my JS file i just need to parse the data using
JSON.parse(data)
The ajax call like
$.get('/recent-dealers', function (data, status) {
console.log (JSON.parse(data)); });
});

Related

How to create simply REST API Login?

I've trying to create login code in REST Server and while I use POSTMAN to check it, the output always show HTTP_BAD_REQUEST(Login Failed). The code is ignoring security.
I use REST from https://github.com/chriskacerguis/codeigniter-restserver
This is My Controller
public function index_post(){
$data_memb = array(
'id_member'=>$this->post('id_member'),
'password'=>$this->post('password')
);
$result = $this->Member_model_api->loginMember($data_memb);
if ($result == TRUE) {
$this->response([
'status' => true,
'message' => 'Login Successfull'
], REST_Controller::HTTP_OK);
} else {
$this->response([
'status' => false,
'message' => 'Login Failed'
], REST_Controller::HTTP_BAD_REQUEST);
}
}
}
This is My Model
public function loginMember($data_memb)
{
$sql = 'SELECT * FROM member WHERE id_member = ?';
$binds = array($data_memb['id_member']);
$query = $this->db->query($sql, $binds);
if ($query->num_rows()>0) {
$rw_password = $query->result();
if (password_verify($data_memb['password'],
$rw_password[0]->password)) {
return true;
} else {
return false;
}
} else {
return false;
}
}
I expect the output is HTTP_OK(Login Successfull), or if you have more reference code, please tell me. Thanks for your help.
Since you are using POST method, you should use form-data while sending request.
I assume there may be some re-formatting issue on the server-side which might be preventing your login functionality to get verified.
Try printing both variables on the server-side and view the output.
var_dump($data_memb)
Nothing needs to send urlencoded in POST request.
Try changing the request type and see, this will work.

Laravel user model not being process in JSON response

I have a Laravel 5.8 API where the JSON response for a user collection works as expected but fails for a model.
namespace App\Traits;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
trait ApiResponder
{
private function successResponse($data, $code)
{
return response()->json($data, $code);
}
protected function errorResponse($message, $code)
{
return response()->json(['error' => $message, 'code' => $code], $code);
}
protected function showAll(Collection $collection, $code = 200)
{
return $this->successResponse(['data' => $collection], $code);
}
protected function showOne(Model $model, $code = 200)
{
return $this->successResponse(['data' => $model], $code);
}
}
Below are the controller methods calling for the response.
public function index()
{
$users = User::all();
return $this->showAll($users);
}
public function update(Request $request, $id)
{
$user = User::findOrFail($id);
$rules = [
'email' => 'email|unique:users,email,' . $user->id,
'password' => 'min:6|confirmed'
];
if ($request->has('name')) {
$user->name = $request->name;
}
if ($request->has('email') && $user->email != $request->email) {
$user->verififed = User::UNVERIFIED_USER;
$user->verififcation_token = User::generateVerificationCode();
$user->email = $request->email;
}
if ($request->has('password')) {
$user->password = bcrypt($request->password);
}
if (!$user->isDirty()) {
return $this->errorResponse('You need to specify a change to update', 422);
}
$user->save();
$this->showOne($user);
}
The index method handle as a collection works perfectly, but the update method using the model returns empty (no content at all). I have confirmed that the $data variable does contain the model information as expected as I can print a JSON encode that displays the result I want. It's just not working in response()->json() for some reason.
Very complex code for what it actually does.
Here you have the problem, needless to say to render the response, you need a return.
$user->save();
$this->showOne($user);
}
should be:
$user->save();
return $this->showOne($user);
}
Bonus: I would look into response transformation for future references see Eloquent Resources or Fractal. Instead of doing to much if logic, you can use FormRequest to validate the input.

Symfony request with json for postman

I have my CRUD but I need to do a POST request with postman. I've been reading some posts but I don't really understand how it works.
My routing for that:
jugador_create:
path: /{_format}
defaults: { _controller: "FutbolBundle:Jugador:new", _format: html }
requirements: { _method: post, _format: html|xml|json }
My controller is this:
public function newAction(Request $request)
{
$entity = new Jugador();
$form = $this->createCreateForm($entity);
if ($request->getMethod() == 'POST'){
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('jugador_show', array('id' => $entity->getId())));
}}
return $this->render('FutbolBundle:Jugador:new.html.twig', array(
'entity' => $entity,
'form' => $form->createView(),
));
}
How do I do it so in postman I can do a JSON post and it creates it? I think I need to do a switch with Json, xml and default case but I don't really understand how to do the Json part.
Thank you so much.
Also, in my index I already did the switch with Json and xml but it's pretty different to a GET than a POST.
public function indexAction(Request $request){
$request = $this->getRequest();
$serializer = new Serializer(array(new GetSetMethodNormalizer()),array(new XmlEncoder(), new JsonEncoder()));
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository('FutbolBundle:Jugador')->findAll();
switch ($request->getRequestFormat()){
case "json":
$response=new Response();
$response->setContent($serializer->serialize($entities,'json'));
return $response;
break;
case "xml":
$response=new Response();
$response->setContent($serializer->serialize($entities,'xml'));
return $response;
break;
default:
return $this->render('FutbolBundle:Jugador:index.html.twig', array(
'entities' => $entities,
));
}
}
My sugestion is as follow:
Implemet a format listener to avoid repeating your code in all controllers
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
if ('json' === $request->getContentType() && $request->getContent()) {
$data = json_decode($request->getContent(), true);
$request->request->replace($data);
}
}
After that your controller should not change because you already transformed the data from json to regular posted data. You can do the same with the xml format or what ever format you want but html.
Specify the headers in postman
At Headers options below the method/url just add the Content-type header as follow.
Content-type(key) application/json (value)
Content-type(key) application/xml (value)
That should work
Hope it helps.
PD: Si no entiendes todo lo que he escrito hazmelo saber.
Just in case anyone needs help, I actually did it pretty simple using sets and the entity of Symfony.
$data = json_decode($request->getContent(),true);
$entity->setNom($data["nom"]);
$entity->setEquip($data["equip"]);
... (more sets if you need them)
and this for inserting it into the data base.
$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();

Symfony ajax objects are null after json_decode

I have an ajax function:
<script type="text/javascript">
$(document).ready(function() {
$('#{{ form.vars.name }}_documentlist').change(function() {
$.ajax({
type: "POST",
data: $("form").serialize(),
});
});
});
</script>
documentlist is a multiselect dropdowns and the selected "entries" are sent to my ActivationController:
/**
* #Route("/document/bulkdeactivate", name="documentBundle_document_bulkDeactivate")
* #Template()
*/
public function bulkDeactivateAction(Request $request) {
$em = $this->getDoctrine()->getManager();
$selected_documents[] = $request->getContent();
dump($selected_documents);
foreach($selected_documents as $document){
$params = json_decode($document, true);
dump($params);
}
$form = $this->createForm(DocumentDeactivationType::class);
$form->handleRequest($request);
if ($form->isSubmitted()) {
foreach($params as $document) {
$document->setActive(false);
$em->persist($document);
$em->flush();
$this->addFlash(
'success',
'The document has been deactivated!'
);
return $this->redirectToRoute('documentBundle_document_list');
}
}
return $this->render('DocumentBundle:Panels:ActivationPanel.html.twig', array(
'form' => $form->createView(),
));
}
But in my controller I'm doing something wrong. I'd like to persist the selected entries to my database and therefor decode them to objects.
Here are my thoughts to my code:
$selected_documents is an array because I can select several documents from my dropdown. I have the foreach loop for the decode because I can't decode an array and I didn't figure out how it would work better.
The first dump($selected_Documents) returns e.g.
array:1 [▼
0 => "search=&document_deactivation%5Bdocumentlist%5D%5B%5D=41"
]
the second dump($params) then returns null so apparently something with my decoding action is going wrong. I've read the documentation but can't really figure out what my mistake is.
Do you need any further information? would be happy about help!

Yii2 Dynamic Form update Action is not working

I like to explain my problem clearly,
Am using wbraganca/yii2-dynamicform
Here create action is working perfectly, but in update action
In the code which i marked, i don't know what i need to do, i dont have such field (addresses) in customer table. am stuck on that.
suppose if i create a variable in model like public $addressess, it makes me the reload the table again, and that cause while update the same form, data's getting reload and form viewing as empty without empty,
if create a function on that name, i don't know what to write on that..
Am simply using code like this
public function getaddressess()
{
}
Create Action Code
public function actionCreate()
{
$modelCustomer = new Customer;
$modelsAddress = [new Address];
if ($modelCustomer->load(Yii::$app->request->post())) {
$modelsAddress = Model::createMultiple(Address::classname());
Model::loadMultiple($modelsAddress, Yii::$app->request->post());
// ajax validation
if (Yii::$app->request->isAjax) {
Yii::$app->response->format = Response::FORMAT_JSON;
return ArrayHelper::merge(
ActiveForm::validateMultiple($modelsAddress),
ActiveForm::validate($modelCustomer)
);
}
// validate all models
$valid = $modelCustomer->validate();
$valid = Model::validateMultiple($modelsAddress) && $valid;
if ($valid) {
$transaction = \Yii::$app->db->beginTransaction();
try {
if ($flag = $modelCustomer->save(false)) {
foreach ($modelsAddress as $modelAddress) {
$modelAddress->customer_id = $modelCustomer->id;
if (! ($flag = $modelAddress->save(false))) {
$transaction->rollBack();
break;
}
}
}
if ($flag) {
$transaction->commit();
return $this->redirect(['view', 'id' => $modelCustomer->id]);
}
} catch (Exception $e) {
$transaction->rollBack();
}
}
}
return $this->render('create', [
'modelCustomer' => $modelCustomer,
'modelsAddress' => (empty($modelsAddress)) ? [new Address] : $modelsAddress
]);
}
Help me to sort out this problem
$modelsAddress=$modelCustomer->addresses in that example mean array of related Address() instances
public function actionCreate()
{
$modelCustomer = new Customer;
$modelsAddress = $this->getaddressess($modelCustomer->id);
//...................
}
public function getaddressess($id)
{
$model = Address::find()->where(['id' => $id])->all();
return $model;
}
from
public function getaddressess($id)
{
$model = Address::find()->where(['id' => $id])->all();
return $model;
}
Shared above you will also need to add
on your Update view file :
'model' => $model,
'modelsAddress'=>$modelsAddress,
Hope this helps. It worked for me
It should be getAddresses() instead of getaddresses() (although both could work, I'd go with the first one to meet conventions). Or you could set a public $addresses if you don't need extra encapsulation.
suppose if i create a variable in model like public $addressess, it makes me the reload the table again, and that cause while update the same form, data's getting reload and form viewing as empty without empty,
I think you have a validation issue - no validator to mark the field as safe and you see it as empty after posting.
Add public $addresses to your Customer model.
Add "addresses" to your validation rules as safe (or more appropriate validator). This way after posting the form, it probably won't render empty.
This line code ---> $modelsAddress = $modelCustomer->addresses;
is get from model for customer at line ---> public function getAddresses()
this public function line code is code for get array related table from active record method on yii2.
$modelCustomer->addresses the word addresses should come from the $modelCustomer model you must have a relationship to the other table where you add the multiple values. In my example described in the video I have two tables po table and po_items table po_items table has foreign key of po_id. So when you make the Models using gii you will get a relationship in the model that is what you have to use instead of the addresses.
the relationship according my database should be - poItems you will see this at line 14
Add this to Customer Model
public function getAddresses(){
return $this->hasMany(Address::className(), ['id' => 'id']);
}
enter image description hereIn Po.php models:
public function getPoItems()
{
return $this->hasMany(PoItem::className(), ['po_id' => 'id']);
}
In PoController.php
public function actionUpdate($id)
{
$model = $this->findModel($id);
//$modelsPoItem = [new PoItem];
$modelsPoItem = $model->poItems;
if ($model->load(Yii::$app->request->post()) && $model->save())
{
$oldIDs = ArrayHelper::map($modelsPoItem, 'id', 'id');
$modelsPoItem = Model::createMultiple(PoItem::classname(), $modelsPoItem);
Model::loadMultiple($modelsPoItem, Yii::$app->request->post());
$deletedIDs = array_diff($oldIDs, array_filter(ArrayHelper::map($modelsPoItem, 'id', 'id')));
// validate all models
$valid = $model->validate();
$valid = Model::validateMultiple($modelsPoItem) && $valid;
if ($valid) {
$transaction = \Yii::$app->db->beginTransaction();
try {
if ($flag = $model->save(false)) {
if (! empty($deletedIDs))
{
PoItem::deleteAll(['id' => $deletedIDs]);
}
foreach ($modelsPoItem as $modelPoItem)
{
$modelPoItem->po_id = $model->id;
if (! ($flag = $modelPoItem->save(false))) {
$transaction->rollBack();
break;
}
}
}
if ($flag) {
$transaction->commit();
return $this->redirect(['view', 'id' => $model->id]);
}
} catch (Exception $e) {
$transaction->rollBack();
}
}
}
return $this->render('update', [
'model' => $model,
'modelsPoItem' => (empty($modelsPoItem)) ? [new PoItem] : $modelsPoItem
]);
}