I want standartise all JSON responses ( something like Standardised JSON response from views ).
Here is JSON response example
{
"status" : "failure",
"errors" : {
"name" : [ "Error text 1", "Error text 2" ],
"email" : [ "Email error text" ]
}
}
And here is class implementing my standartised JSON response
class JsonResponse
{
protected $_errors = array();
public function addError($key, $value) {
$this->_errors[$key][] = $value;
return $this;
}
public function setFormErrors(Zend_Form_Abstract $form) {
$this->_errors = $form->getErrors();
return $this;
}
public function __toArray() {
if (!empty($this->errors)) {
return array(
'status' => 'fail',
'errors' => $errors,
);
}
return array(
'status' => 'success',
);
}
public function __toString() {
return json_encode($this->__toArray());
}
}
Everything is great, but using this class is real pain in the ass.
class App_Controller extends Zend_Action_Controller
{
public function submitAction()
{
$form = new App_Form();
$form->isValid($this->getRequest()->getPost());
//disabling displaying layout
$this->_helper->layout()->disableLayout();
$this->_helper->viewRenderer->setNoRender(true);
$response = new JsonResponse();
$response->setFormErrors($form);
echo $response;
}
}
I'm real noob in Zend Framework.
What is the best way to wrap JsonResponse to?
helper
extend Zend_Controller_Response_Abstract
new context switch
implement in Form
What is the best approach?
Creating a custom JSON response object with Zend Action Helper ContextSwitch - nice way of doing it, but still requires too much code, imho.
IMO you should use a context switch (JSON or your own) and use your preferred way to format your response data structure.
It seems like you are implementing the ajax form validation aren't you ? Maybe you should have a look on this : Zend form ajax validation on submit (and ask google about processAjax())
Related
I am fairly new to Symfony 5.4 and recently created my first API using that version
For my specific API endpoint one of the parameters is an array of IDs.
I need to validate this array in the following way:
make sure that this IS an array;
make sure that IDs in the array actually refer to database records;
I implemented it in a straightforward way where I check the array before persisting the entity using typecasting and existing Repository:
$parentPropertyIds = (array)$request->request->get('parent_property_ids');
if ($parentPropertyIds) {
$parentCount = $doctrine->getRepository(Property::class)->countByIds($parentPropertyIds);
if ($parentCount !== count($parentPropertyIds)) {
return $this->json([
'status' => 'error',
'message' => 'parent_property_id_invalid'
], 422);
}
foreach ($parentPropertyIds as $parentPropertyId) {
$parentProperty = $doctrine->getRepository(Property::class)->find($parentPropertyId);
$property->addParent($parentProperty);
}
}
However, this makes my controller action become too "body-positive" and also feels like something that could be implemented in a more elegant way.
I was unable to find anything in Symfony 5.4 docs.
At the moment I am wondering if:
there is a way to filter/sanitize request parameter available in Symfony;
there is an elegant built-in way to apply custom validator constraint to a request param (similar to well-documented entity field validation);
Full endpoint code:
/**
* #Route("/property", name="property_new", methods={"POST"})
*/
public function create(ManagerRegistry $doctrine, Request $request, ValidatorInterface $validator): Response
{
$entityManager = $doctrine->getManager();
$property = new Property();
$property->setName($request->request->get('name'));
$property->setCanBeShared((bool)$request->request->get('can_be_shared'));
$parentPropertyIds = (array)$request->request->get('parent_property_ids');
if ($parentPropertyIds) {
$parentCount = $doctrine
->getRepository(Property::class)
->countByIds($parentPropertyIds);
if ($parentCount !== count($parentPropertyIds)) {
return $this->json([
'status' => 'error',
'message' => 'parent_property_id_invalid'
], 422);
}
foreach ($parentPropertyIds as $parentPropertyId) {
$parentProperty = $doctrine->getRepository(Property::class)->find($parentPropertyId);
$property->addParent($parentProperty);
}
}
$errors = $validator->validate($property);
if (count($errors) > 0) {
$messages = [];
foreach ($errors as $violation) {
$messages[$violation->getPropertyPath()][] = $violation->getMessage();
}
return $this->json([
'status' => 'error',
'messages' => $messages
], 422);
}
$entityManager->persist($property);
$entityManager->flush();
return $this->json([
'status' => 'ok',
'id' => $property->getId()
]);
}
You could use a combination of Data Transfer Object (DTO) with Validation service. There is a number of predefined constraints or you could create a custom one.
For expamle, how to use simple constraint as an annotation:
class PropertyDTO {
/**
* #Assert\NotBlank
*/
public string $name = "";
public bool $shared = false;
}
Then assign data to DTO:
$propertyData = new PropertyDTO();
$propertyData->name = $request->request->get('name');
...
In some cases it is a good idea to define a constructor in the DTO, then get all data from the request and pass it to DTO at once:
$data = $request->getContent(); // or $request->getArray(); depends on your content type
$propertyData = new PropertyDTO($data);
Then validate it:
$errors = $validator->validate($propertyData);
if (count($errors) > 0) {
/*
* Uses a __toString method on the $errors variable which is a
* ConstraintViolationList object. This gives us a nice string
* for debugging.
*/
$errorsString = (string) $errors;
return $this->json([
'status' => 'error',
'message' => 'parent_property_id_invalid'
], 422);
}
//...
Hey Guys I have the following Scenario and I can't think of a better way. Maybe you guys can provide a more DRY method
So update method BaseController from Laravel Voyager
public function update(Request $request, $id)
{
// Update Logic Here
// Redirect Logic
if (auth()->user()->can('browse', app($dataType->model_name))) {
$redirect = redirect()->route("voyager.{$dataType->slug}.index");
} else {
$redirect = redirect()->back();
}
}
return $redirect->with([
'message' => __('voyager::generic.successfully_updated')." {$dataType->getTranslatedAttribute('display_name_singular')}",
'alert-type' => 'success',
]);
Custom Controller that extends the above Base Controller
public function update(Request $request, $id)
{
// Update Logic Copied and Pasted from Base ( Yuck :( )
// Small Change to the Redirect Logic
if (auth()->user()->can('browse', app($dataType->model_name))) {
$redirect = redirect()->route("voyager.{$dataType->slug}.index");
} else {
$redirect = redirect()->route("voyager.{$dataType->slug}.show",$id);
}
}
return $redirect->with([
'message' => __('voyager::generic.successfully_updated')." {$dataType->getTranslatedAttribute('display_name_singular')}",
'alert-type' => 'success',
]);
So my question is with the current structure of the Base Controller Is there any other way to override the redirect logic without literally copying and pasting the whole lot of code
I do not want to edit the BaseController as it will stop me from updating the package
Any thoughts would be great
Cheer
Simply use smaller functions to extract that logic and override it, similar approaches with overriding function through inheritance for changing logic, is used by Laravel on Models see getRouteKey() for example.
In your BaseController.php, i would split it up like so.
{
if (auth()->user()->can('browse', app($dataType->model_name))) {
$redirect = redirect()->route("voyager.{$dataType->slug}.index");
} else {
$redirect = $this->browseRedirectLocation();
}
}
return $redirect->with([
'message' => __('voyager::generic.successfully_updated')." {$dataType->getTranslatedAttribute('display_name_singular')}",
'alert-type' => 'success',
]);
}
protected function browseRedirectLocation() {
{
return $redirect = redirect()->back();
}
Now you should be able to override redirect location in your CustomController.php, instead of the whole function in your implementation class. As i could see it was only the redirect that was changed.
protected function browseRedirectLocation() {
{
return redirect()->route("voyager.{$dataType->slug}.show",$id);
}
I have built a website using php and the zend framework. In one of the pages I have a zend form and a table. The user can fill in the form, click the search button(page refresh occurs) and then get the corresponding results in the table.
What I am trying to do is to implement the same functionality using Ajax so the page won't have to refresh or ask for re-submission when reloaded.
From my controller I pass the data I want to display to view.phtml.
When the page first opens all the data from database gets displayed in the table. Somehow after the user clicks search :
the ajax post data should be retrieved in the controller
compared to the rest of the data to see if there are any matches
return the data matched
public function searchAction(): ViewModel
{
$persons = $this->personsService->getAllPersons();
$form = $this->personsForm;
if ($this->getRequest()->isPost()) {
$formData = $this->params()->fromPost();
$form->setData($formData);
if ($form->isValid()) {
$validFilteredData = $form->getData();
$persons = $this->personsService->getPersonsAfterSearch($validFilteredData);
}
}
return new ViewModel([
'persons' => $persons,
'form' => $form,
]);
}
I would like any suggestions on how to implement ajax since I am a beginner in web development and I don't experience working with ajax.
Thanks in advance.
Before you do this:
return new ViewModel([
'persons' => $persons,
'form' => $form,
]);
Add this:
if ($this->getRequest()->isXmlHttpRequest()) {
return new \Zend\View\Model\JsonModel(
[
'persons' => $persons,
'form' => $form,
]
);
}
Note: you've tagged "zend-framework" but mentioned "zend3". Above solution works for ZF2 and ZF3, don't know about ZF1.
Update due to comments:
Full function would be:
public function searchAction() : ViewModel
{
$persons = $this->personsService->getAllPersons();
$form = $this->personsForm;
if ($this->getRequest()->isPost()) {
$formData = $this->params()->fromPost();
$form->setData($formData);
if ($form->isValid()) {
$validFilteredData = $form->getData();
$persons = $this->personsService->getPersonsAfterSearch($validFilteredData);
}
}
$data = [
'persons' => $persons,
'form' => $form,
];
// AJAX response
if ($this->getRequest()->isXmlHttpRequest()) {
return new \Zend\View\Model\JsonModel($data);
}
return $data; // No need to return "new ViewModel", handled via ZF magic
}
I'm trying to figure out why my ApiException is still returning a text/html response instead of a json response as denoted in ApiException render method. It is giving me the correct error message however its not rendering it as json.
/**
* Get the checklist (depending on type - send from Vue model)
*/
public function fetchChecklist(Request $request)
{
$id = $request->input('projectId');
$type = $request->input('type');
if (empty($id)) {
throw new ApiException('Project was not provided.');
}
if (! $project = RoofingProject::find($id)) {
throw new ApiException('Project not found.');
}
if (empty($type)) {
throw new ApiException('No checklist type was provided.');
}
switch ($request->input('type')) {
case 'permitting':
$items = $project->permittingChecklist;
break;
case 'permit':
$items = $project->permitReqChecklist;
break;
default:
throw new ApiException('Checklist not found.');
break;
}
return [
'status' => 'success',
'message' => '',
'items' => $items
];
}
App\Exceptions\ApiException.php
<?php
namespace App\Exceptions;
class ApiException extends \Exception
{
public function render($request)
{
return response()->json(['status' => 'error', 'error' => $this->message]);
}
}
In your request to the API you can try to add the following to your head/curl call to specify the datatype:
"Accept: application/json"
The laravel application is looking for if the requests expects json.
It worked for me with setting the following header as so
"x-requested-with": "XMLHttpRequest"
I'm still trying to add a recaptcha to my website, I want try the recaptcha from Google but I can't use it properly. Checked or not, my email is still sent.
I tried to understand the code of How to validate Google reCaptcha v2 using phalcon/volt forms?.
But i don't understand where are my problems and more over how can you create an element like
$recaptcha = new Check('recaptcha');
My controller implementation :
<?php
/**
* ContactController
*
* Allows to contact the staff using a contact form
*/
class ContactController extends ControllerBase
{
public function initialize()
{
$this->tag->setTitle('Contact');
parent::initialize();
}
public function indexAction()
{
$this->view->form = new ContactForm;
}
/**
* Saves the contact information in the database
*/
public function sendAction()
{
if ($this->request->isPost() != true) {
return $this->forward('contact/index');
}
$form = new ContactForm;
$contact = new Contact();
// Validate the form
$data = $this->request->getPost();
if (!$form->isValid($data, $contact)) {
foreach ($form->getMessages() as $message) {
$this->flash->error($message);
}
return $this->forward('contact/index');
}
if ($contact->save() == false) {
foreach ($contact->getMessages() as $message) {
$this->flash->error($message);
}
return $this->forward('contact/index');
}
$this->flash->success('Merci, nous vous contacterons très rapidement');
return $this->forward('index/index');
}
}
In my view i added :
<div class="g-recaptcha" data-sitekey="mypublickey0123456789"></div>
{{ form.messages('recaptcha') }}
But my problem is after : i create a new validator for the recaptcha like in How to validate Google reCaptcha v2 using phalcon/volt forms? :
use \Phalcon\Validation\Validator;
use \Phalcon\Validation\ValidatorInterface;
use \Phalcon\Validation\Message;
class RecaptchaValidator extends Validator implements ValidatorInterface
{
public function validate(\Phalcon\Validation $validation, $attribute)
{
if (!$this->isValid($validation)) {
$message = $this->getOption('message');
if ($message) {
$validation->appendMessage(new Message($message, $attribute, 'Recaptcha'));
}
return false;
}
return true;
}
public function isValid($validation)
{
try {
$value = $validation->getValue('g-recaptcha-response');
$ip = $validation->request->getClientAddress();
$url = $config->'https://www.google.com/recaptcha/api/siteverify'
$data = ['secret' => $config->mysecretkey123456789
'response' => $value,
'remoteip' => $ip,
];
// Prepare POST request
$options = [
'http' => [
'header' => "Content-type: application/x-www-form-urlencoded\r\n",
'method' => 'POST',
'content' => http_build_query($data),
],
];
// Make POST request and evaluate the response
$context = stream_context_create($options);
$result = file_get_contents($url, false, $context);
return json_decode($result)->success;
}
catch (Exception $e) {
return null;
}
}
}
So i don't know if tjis code is correct anyway, i have a problem too after that : how to create an object "recaptcha" in my form add
$recaptcha = new ?????('recaptcha');
$recaptcha->addValidator(new RecaptchaValidator([
'message' => 'Please confirm that you are human'
]));
$this->add($recaptcha);
PS: I apologize because i'm a noob here and my mother tongue is not english, so if you don't understand me or want give me some advices to create a proper question, don't hesitate ^^
I've made a custom form element for recaptcha. Used it for many projects so far.
The form element class:
class Recaptcha extends \Phalcon\Forms\Element
{
public function render($attributes = null)
{
$html = '<script src="https://www.google.com/recaptcha/api.js?hl=en"></script>';
$html.= '<div class="g-recaptcha" data-sitekey="YOUR_PUBLIC_KEY"></div>';
return $html;
}
}
The recaptcha validator class:
use Phalcon\Validation\Validator;
use Phalcon\Validation\ValidatorInterface;
use Phalcon\Validation\Message;
class RecaptchaValidator extends Validator implements ValidatorInterface
{
public function validate(\Phalcon\Validation $validation, $attribute)
{
$value = $validation->getValue('g-recaptcha-response');
$ip = $validation->request->getClientAddress();
if (!$this->verify($value, $ip)) {
$validation->appendMessage(new Message($this->getOption('message'), $attribute, 'Recaptcha'));
return false;
}
return true;
}
protected function verify($value, $ip)
{
$params = [
'secret' => 'YOUR_PRIVATE_KEY',
'response' => $value,
'remoteip' => $ip
];
$response = json_decode(file_get_contents('https://www.google.com/recaptcha/api/siteverify?' . http_build_query($params)));
return (bool)$response->success;
}
}
Using in your form class:
$recaptcha = new Recaptcha($name);
$recaptcha->addValidator(new RecaptchaValidator([
'message' => 'YOUR_RECAPTCHA_ERROR_MESSAGE'
]));
Note 1: You were almost there, you just missed to create custom form element (the first and last code piece from my example);
Note 2: Also there is a library in Github: https://github.com/fizzka/phalcon-recaptcha I have not used it, but few peeps at phalcon forum recommended it.