I have this code:
public function saveAction(Request $request)
{
$orders = $request->get('orders');
$sameAddress = $request->get('same_address');
// NaturalPerson: 1 | LegalPerson: 2
$person_type = isset($orders['person']['nat']) ? 1 : 2;
$register_type = isset($orders['person']['nat']) ? array("natural") : array("legal");
$entityOrder = new Orders();
$formOrder = $this->createForm(new OrdersType($register_type), $entityOrder);
$formOrder->handleRequest($request);
$em = $this->getDoctrine()->getManager();
$em->getConnection()->beginTransaction();
$errors = "";
$is_new = false;
if ($formOrder->isValid())
{
if ($person_type === 1)
{
// Set NaturalPerson entity
$entityPerson = $em->getRepository('FrontendBundle:NaturalPerson')->findOneBy(array("ci" => $orders['person']['ci']));
if (!$entityPerson)
{
$entityPerson = new NaturalPerson();
$entityPerson->setPersonType($person_type);
$entityPerson->setDescription($orders['person']['nat']['description']);
$entityPerson->setContactPerson($orders['person']['nat']['contact_person']);
$entityPerson->setIdentificationType($orders['person']['identification_type']);
$entityPerson->setCI($orders['person']['ci']);
$is_new = true;
}
}
elseif ($person_type === 2)
{
// Set LegalPerson entity
$entityPerson = $em->getRepository('FrontendBundle:LegalPerson')->findOneBy(array("rif" => $orders['person']['rif']));
if (!$entityPerson)
{
$entityPerson = new LegalPerson();
$entityPerson->setPersonType($person_type);
$entityPerson->setDescription($orders['person']['leg']['description']);
$entityPerson->setContactPerson($orders['person']['leg']['contact_person']);
$entityPerson->setIdentificationType($orders['person']['identification_type']);
$entityPerson->setRIF($orders['person']['rif']);
$is_new = true;
}
}
....
$entityOrder->setPerson($entityPerson);
$em->persist($entityOrder);
$em->flush();
}
else
{
$this->get('ladybug')->log($this->getFormErrors($formOrder));
$message = 'ERROR AL PROCESAR LOS DATOS';
$em->getConnection()->rollback();
}
return $this->render('FrontendBundle:Site:process.html.twig', array('message' => $message, 'errors' => $errors));
}
For some reason in somewhere, that I can not find, to the entity is arriving a Array as stacktrace show in this line:
at Orders ->setPerson (array('rif' => '5345345345', 'identification_type' => 'V', 'description' => 'uiyiyuiyuiy',
'contact_person' => 'ertertet'))
in /var/www/html/tanane/vendor/symfony/symfony/src/Symfony/Component/PropertyAccess/PropertyAccessor.php
at line 438
Which is causing this issue on my application:
Catchable Fatal Error: Argument 1 passed to
Tanane\FrontendBundle\Entity\Orders::setPerson() must be an instance
of Tanane\FrontendBundle\Entity\Person, array given, called in
/var/www/html/tanane/vendor/symfony/symfony/src/Symfony/Component/PropertyAccess/PropertyAccessor.php
on line 438 and defined in
/var/www/html/tanane/src/Tanane/FrontendBundle/Entity/Orders.php line
276
Can any give me any idea in where to look or find for this error?
Running some tests
After running some test (fill the form and send the data as any normal user would) I'm confused and don't know what else to do for fix the issue. The application have two type of forms to handle Orders: Natural and Legal. I test the first one Natural and all was fine, the form validates and the flow was completely without problems. Now if I go trough the second form the the error described above appears, why? Is the same process exactly and values are OK since $person_type is taking 2 and it's a integer so, any advice? I'm getting crazy at this point
Well after having made several test finally found what was wrong, I complety forgot this function at LegalPersonType.php form:
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Tanane\FrontendBundle\Entity\LegalPerson'
));
}
And that was causing the issue, thanks for your help and time
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);
}
//...
I am new at PHP. We are creating REST API in Phalcon and I've created a put request. It already works, but I would like to check if update has really happened before sending a success response. So I've created a conditional for that ( if (!$product->update()) ), but it always returns 'true'. How can I check if any field has changed in a record?
public function put()
{
$id = $this->getParam('id');
$input = $this->getRawData();
$product = Product::findFirst([
'conditions' => 'id = :id:',
'bind' => ['id' => $id]
]);
if ($product === null){
throw new NotFoundException();
}
$product->assign($input);
$product->update();
if (!$product->update()) {
$this->errorResponse($product->getMessages());
} else {
$this->successResponse($product->toArray($product->update()));
}
}
You can use Model Events, i.e. afterUpdate and notSaved, like:
use Phalcon\Mvc\Model;
use Phalcon\Http\Response;
class ModelBase extends Model
{
public function afterUpdate()
{
$response = new Response();
$response->setJsonContent([
'success' => true,
'message' => "Record updated"
])->send();
}
public function notSaved()
{
$response = new Response();
$response->setJsonContent([
'success' => false,
'message' => 'Record not saved'
])->send();
}
}
The Product and all other models will extend ModelBase. Then your code could be:
public function put()
{
$id = $this->getParam('id');
$input = $this->getRawData();
$product = Product::findFirst([
'conditions' => 'id = :id:',
'bind' => ['id' => $id]
]);
if ($product === null){
throw new NotFoundException();
}
$product->assign($input);
$product->update();
}
And Phalcon event will respond if the model was updated or not. If you prefer, you can also use custom http response codes for update or notSaved. More information about Model Events in the documentation
You are calling $product->update() three times. You do it once after the assign, then again for your if test, which is why it's always returning TRUE there I believe, and once inside the toArray() which may not actually return anything since the second and third updates don't have any data to update (not sure about that though).
I would code this as follows:
$product->assign($input);
$results = $product->update();
if (!results) {
$this->errorResponse($product->getMessages());
} else {
$this->successResponse($results->toArray());
}
I am assuming that the $product->assign($input); statement is working as expected to update the $product data for you. I don't use that. I prefer to do direct assignments for updates so nothing is left to chance, ie. $product->whatever = $input['whatever'];.
Give this a try and hopefully it will work as expected for you.
I'm completely lost as to why this is happening, and it happens about 50% of the time.
I have a check to see if a user exists by email and last name, and if they do, run some code. If the user doesn't exist, then create the user, and then run some code.
I've done various testing with dummy data, and even if a user doesn't exist, it first creates them, but then runs the code in the "if" block.
Here's what I have.
if (User::existsByEmailAndLastName($params->email, $params->lastName)) {
var_dump('user already exists');
} else {
User::createNew($params);
var_dump("Creating a new user...");
}
And here are the respective methods:
public static function existsByEmailAndLastName($email, $lastName) {
return User::find()->where([
'email' => $email,
])->andWhere([
'last_name' => $lastName
])->one();
}
public static function createNew($params) {
$user = new User;
$user->first_name = $params->firstName;
$user->last_name = $params->lastName;
$user->email = $params->email;
$user->address = $params->address;
$user->address_2 = $params->address_2;
$user->city = $params->city;
$user->province = $params->province;
$user->country = $params->country;
$user->phone = $params->phone;
$user->postal_code = $params->postal_code;
return $user->insert();
}
I've tried flushing the cache. I've tried it with raw SQL queries using Yii::$app->db->createCommand(), but nothing seems to be working. I'm totally stumped.
Does anyone know why it would first create the user, and then do the check in the if statement?
Editing with controller code:
public function actionComplete()
{
if (Yii::$app->basket->isEmpty()) {
return $this->redirect('basket', 302);
}
$guest = Yii::$app->request->get('guest');
$params = new CompletePaymentForm;
$post = Yii::$app->request->post();
if ($this->userInfo || $guest) {
if ($params->load($post) && $params->validate()) {
if (!User::isEmailValid($params->email)) {
throw new UserException('Please provide a valid email.');
}
if (!User::existsByEmailAndLastName($params->email, $params->lastName)) {
User::createNew($params);
echo "creating new user";
} else {
echo "user already exists";
}
}
return $this->render('complete', [
'model' => $completeDonationForm
]);
}
return $this->render('complete-login-or-guest');
}
Here's the answer after multiple tries:
Passing an 'ajaxParam' parameters with the ActiveForm widget to define the name of the GET parameter that will be sent if the request is an ajax request. I named my parameter "ajax".
Here's what the beginning of the ActiveForm looks like:
$form = ActiveForm::begin([
'id' => 'complete-form',
'ajaxParam' => 'ajax'
])
And then I added this check in my controller:
if (Yii::$app->request->get('ajax') || Yii::$app->request->isAjax) {
return false;
}
It was an ajax issue, so thanks a bunch to Yupik for pointing me towards it (accepting his answer since it lead me here).
You can put validation like below in your model:
public function rules() { return [ [['email'], 'functionName'], [['lastname'], 'functionforlastName'], ];}
public function functionName($attribute, $params) {
$usercheck=User::find()->where(['email' => $email])->one();
if($usercheck)
{
$this->addError($attribute, 'Email already exists!');
}
}
and create/apply same function for lastname.
put in form fields email and lastname => ['enableAjaxValidation' => true]
In Create function in controller
use yii\web\Response;
if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) {
Yii::$app->response->format = Response::FORMAT_JSON;
return ActiveForm::validate($model);
}
else if ($model->load(Yii::$app->request->post()))
{
//place your code here
}
Add 'enableAjaxValidation' => false to your ActiveForm params in view. It happens because yii sends request to your action to validate this model, but it's not handled before your if statement.
How to test specific validation errors in php unit thrown in validation error ?
with below code we could check session has errors, but not the exact error
$this->assertSessionHasErrors();
assertSessionHasErrors can receive an array, as documented:
$this->assertSessionHasErrors([
'field' => 'Field error message.'
]);
Got the answer
$errors = session('errors');
$this->assertSessionHasErrors();
$this->assertEquals($errors->get('name')[0],"Your error message for validation");
$errors is MessageBag object which stored in laravel session when validation error thrown
using $errors->get('name') you could see all the validation errors as an array
You may use the combination of assertStatus and assertJson
...
->assertStatus(422)
->assertJson([
'errors' => [
'field' => [
'Error message'
]
]
]);
You can use $response->assertSessionHasErrors('key')
https://laravel.com/docs/7.x/http-tests#assert-session-has-errors
an example for required attribute will be
$response = $this->json('POST', '/api/courses', $this->data([
'name' => '',
'api_token' => $this->user->api_token
]));
$response->assertSessionHasErrors('name');
You can add an extra assertion, to make sure that no entry was added to the database, in this case "assert no course was added"
$this->assertCount(0, Course::all());
For multiple required attributes you may use a loop something like the following:
collect(['name', 'description', 'amount'])->each(function ($field) {
$response = $this->json('POST', '/api/courses', $this->data([
$field => '',
'api_token' => $this->user->api_token
]));
$response->assertSessionHasErrors($field);
$this->assertCount(0, Course::all());
});
First I use
$this->post()
instead of
$this->jsonPost()
Dont know why, for certain reason, the session would not come out.
Then I just use
$response->assertSessionHasErrors('field_name', 'Error Message!');
To find out what are the error message, you must dump it
$response->dumpSession();
There is also a more elegant way in my opinion:
If you throw an exception via the class GeneralException you can check in a unit test if the session has a flash_danger from throwing a exception.
Lets do a practical example: We want to test that the admin cannot activate an already activated catalogue item.
Test function
public function an_admin_cannot_activate_an_activated_catalogue()
{
$catalogue = factory(Catalogue::class)->states('active')->create();
$response = $this->get("/admin/questionnaire/catalogue/{$catalogue->id}/activate");
$response->assertSessionHas(['flash_danger' => __('The catalogue item is already activated.')]);
}
Model/Repro function
If it is activated we throw an Exception which then can be checked by the test function.
public function activate(Catalogue $catalogue) : Catalogue
{
if ($catalogue->is_active) {
throw new GeneralException(__('The catalogue item is already activated.'));
}
$catalogue->is_active = 1;
$activated = $catalogue->save();
if($activated) {
return $catalogue;
}
}
actually you can easily throw errors from validation using dd() and session('errors')
since errors bag is stored in session you could add dd(session('errors')) in your unit tests to see which fields you are missing.
and finally you can write more proper test by adding $response->assertSessionHasErrors('field_name');
Laravel 7;
In my case, I needed to ensure there was no error.
But below did ignore form-validation errors (at least mine).
$response->assertSessionHasNoErrors();
Hence I created a custom assert function in base TestCase class, like:
use PHPUnit\Framework\Constraint\RegularExpression;
// ...
public static function assertNoErrorReport(TestResponse $response)
{
$error = static::getViewError($response);
if ( ! empty($error)) {
$this->fail('View contains error:' . PHP_EOL . $error);
}
$response->assertSessionHasNoErrors();
}
public function assertHasErrorRegExp(string $pattern, TestResponse $response, string $message = '')
{
$error = static::getViewError($response);
static::assertThat($error, new RegularExpression($pattern),
empty($message) ? $error : $message);
}
public static function getViewError(TestResponse $response)
{
$content = $response->getOriginalContent();
if ( ! $content) {
static::fail('View content missing.');
}
if ($content instanceof View) {
$data = $content->gatherData();
$error = $data['error'] ?? $data['errors'] ?? null;
// Casts array to string.
if (is_array($error)) {
$error = '[' . join(', ', $error) . ']';
}
// Casts Error-bag to string.
$error = '' . $error;
if ($error === '[]') {
return null;
}
} else {
static::fail('Response is not a View.');
}
return $data;
}
However, my assertHasErrorRegExp(...) could be used for OP's case.
I'm trying to do a chat system, I'm having troubles with it, my ideia is the following:
When the user send a message, this message has to be saved in 2 tables in my database... But it isn't working, the message is saving in only one table.
public function sendMessage()
$data = $this->request->data();
$sessionId = $data['idSession'];
$userId = $this->request->session()->read('Auth.User.id');
$msg = $data['message'];
$typeMessage = $data['type'];
$messageTable = TableRegistry::get('messages');
$messageAllTable = TableRegistry::get('mensage_alls');
if ($typeMessage == 1)
{
$message['session_private_id'] = $sessionId;
}
else
{
$message['session_id'] = $sessionId;
}
$message = array_merge($message, array(
'user_id' => $userId,
'message' => $msg,
'created_at' => new \DateTime(date("Y-m-d H:i:s")),
));
$messageEntity = $messageTable->newEntity();
$messageEntity = $messageTable->patchEntity($messageEntity, $message, ['validate' => false]);
$resposta = $messageTable->save($messageEntity);
$this->response->body($resposta);
return $this->response;
I'm a beginner in CakePHP, so, don't need to call me a dumb.
Thanks since now. And sorry for my bad english.
Ignoring the reason why you'd want to duplicate the data (it doesn't sound like a good thing generally speaking), the reason it doesn't work is you never saved it to the 2nd table. You need to call something at minimum like:
$resposta = $messageTable->save($messageEntity);
$messageCopy = $messageAllTable->newEntity($messageEntity);
$respostaCopy = $messageAllTable->save($messageCopy);
$this->response->body($resposta && $respostaCopy);
If you meant you want to to always automatically copy to a secondary table, then you could instead add a Behavior to the main Message Table. For example, a bare-bones version would look like:
In MessageTable.php:
namespace App\Model\Table;
use Cake\ORM\Table;
class MessageTable extends Table
{
public function initialize(array $config)
{
// Add this line
$this->addBehavior('CopyMessage');
}
}
And create a src/Model/Behavior/CopyMessageBehavior.php:
namespace App\Model\Behavior;
use Cake\ORM\Behavior;
class CopyMessageBehavior extends Behavior
{
public function copyMessage(Entity $entity)
{
$messageAllTable = TableRegistry::get('mensage_alls');
$messageCopy =messageAllTable->newEntity($messageEntity);
$messageAllTable->save($messageCopy);
}
public function beforeSave(Event $event, EntityInterface $entity)
{
$this->copyMessage($entity);
}
}