CakePHP - Custom validation for linked model only in certain parent models - validation

I have a generic Image model that is linked to by other models that need to have images attached. In most places the image is not required and we have fallbacks in case there is no image uploaded, but in a few particular cases I need to force the upload of an image for the form to validate, but I'm not sure how to validate that through another model. For instance, my model is something like this:
class Person extends AppModel
{
public $belongsTo = array(
'Image' => array(
'className' => 'Image',
'foreignKey' => 'image_id',
'type' => 'LEFT',
)
);
public $validate = array(
...
);
}
The Person model contains some text fields that folks have to enter as well as a redirect_url field. If a redirect is set the page logic will skip trying to load anything and will redirect directly to that URL. But, if it is not set then a bunch of other fields are required. I've got this working properly using a custom validation method in my Person model, but image_id field is not explicitly checked by the Person model since it is just a pointer to the Image model.
Can I somehow add a custom/dynamic validation rule to Image in this instance to have it check if Person.redirect_url is set? The only thing I can figure to do is to add this to my beforeSave() and basically manually check it using $this->data but I'd like to do this the "right" way if it's possible, hooking into the Validation class.
I tried a few variations on using something like this, with no luck thus far:
$this->Person->Image->validate['id']=array(...);
Edit:
Here is what I've tried doing, which kind of works:
public function beforeValidate($options=array()) {
parent::beforeValidate($options);
if(empty($this->data['redirect_url'])) {
if (!isset($this->data['Image']['filepath']) {
$this->invalidate('Image.filepath', 'Custom error message.');
return false;
}
}
}
This lets me invalidate the field without having to add extra code elsewhere, but when printing out the form field on the front end, I end up getting a generic "This file is required" error instead of my "Custom error message". I think this might be because file uploads are handled by a plugin that spirits them away to S3 instead of the local filesystem and it's getting overridden somewhere up the chain.

Related

Conditionally loaded data in API resource: How to "pass" a condition?

I got a little issue to solve. In my app I am handling with a lot of Models and each model does have something like:
ModelResource
ModelResourceCollection
ModelResourceOverview
ModelResourceOverviewCollection
The reason is: Sometimes I don't need all the information that are visible if I am using the ModelResource - in this case I am calling the ModelResourceOverview.
Example
PostResource
- title
- tags
- content
- author
Example
PostOverviewResource
- title
- author
As I have a lot of Models I have a huge number of ApiResource-Classes and I want to get rid of that.
I thought about using $this->when in the Resource and pass something like "full" or "overview" to the request in the Controller.
Example
$data
= new PostResource($this->post);
So my question is: Is it the best way to add this to the request or is there a handier/nicer way to handle this?
Laravel has a way to hide fields on the fly: Hiding attributes from json
e.g.
return $post->makeHidden('content')->toArray();
If your logic about "visible or not" is bound to the current request, then you should use when or mergeWhen as you mentioned (everything here https://laravel.com/docs/7.x/eloquent-resources#conditional-attributes ), therefore you'll only have 2 resources instead of 4
public function toArray($request)
{
return [
'title' => $this->title,
'author' => $this->author,
$this->mergeWhen($this->needsFullData($request), [
'tags' => $this->tags,
'content' => $this->content,
]),
];
}
protected function needsFullData($request)
{
//Your logic
}

Controller and Routes in Laravel

What basically is the difference between Controller and Routes. We can control our data using routes file, then why do we need controllers?
Like:
<?php
// app/routes.php
// route to process the ducks form
Route::post('ducks', function()
{
// process the form here
// create the validation rules ------------------------
$rules = array(
'name' => 'required', // just a normal required validation
'email' => 'required|email|unique:ducks', // required and must be unique in the ducks table
'password' => 'required',
'password_confirm' => 'required|same:password' // required and has to match the password field
);
// do the validation ----------------------------------
// validate against the inputs from our form
$validator = Validator::make(Input::all(), $rules);
// check if the validator failed -----------------------
if ($validator->fails()) {
// get the error messages from the validator
$messages = $validator->messages();
// redirect our user back to the form with the errors from the validator
return Redirect::to('ducks')
->withErrors($validator);
} else {
// validation successful ---------------------------
// our duck has passed all tests!
// let him enter the database
// create the data for our duck
$duck = new Duck;
$duck->name = Input::get('name');
$duck->email = Input::get('email');
$duck->password = Hash::make(Input::get('password'));
// save our duck
$duck->save();
// redirect ----------------------------------------
// redirect our user back to the form so they can do it all over again
return Redirect::to('ducks');
}
});
Well, this is not my code, I read it somewhere, But, here this person has used the validation in routes.php file, and in my project, I used the validation technique in a controller named UserController, what difference does it make?
Routes translate each incoming HTTP request to an action call, for example to a method of a controller, whereas controller is the place where business logic are written. There is nothing wrong in handling all in one file, but once your projects gets bigger it would be nightmare to manage such code. It's like responsibility, route, route the request to specific controller, controller process it, pass result to view. Mostly it's design pattern.
We can even have all the code in one huge file without using any classes at all, but we know that is not a good idea. The current best practice is to separate the code depending on responsibilities (single responsibility principle) to make it easier to other developers to read and understand the code. Often the next developer is yourself in some months, so having a clean structure don't only benefit others but also your sanity when going back to your old code.
The name router imply that the class routs data, in this case from an URI to a controller and the controller handle the business rules for that particular controller
Routes in laravel is a place where you define your application end points and controller is where you write your business logic.
I had the same problem understanding Laravel when I started to learn and to make it simple, I have created some project in MCV style please check this
https://github.com/jagadeshanh/understanding-laravel

Custom Magento Module

So im creating a module in the backend, I have a shell module created (items in admin top menu and a page to visit.) basically I want to have an input field that the admin can type a number into then click a button "add", this will insert a row into an existing table in the database.
$connection = Mage::getSingleton('core/resource')->getConnection('core_write');
$connection->beginTransaction();
$fields = array();
$fields['name']= 'andy';
$connection->insert('test', $fields);
$connection->commit();
I have a table called "test" within my database. If I put the above code into my Controller file, it successfully adds a row to the database when i visit the admin page. But i need to allow the user to input the data that is inserted.
Would I have to move that code into the Model and somehow send the input data to the Model and let that do the work? or not. If this is correct could someone point me to a good place to research sending data to models? (if thats even possible)
iv tried lots of tutorials but they are all way to big for what I need, I dont need to display anything, I just need to have an input box and a save button.
EDIT
i have created a file block/Adminhtml/Form/Edit/Form.php which contains the following . . .
class AndyBram_Presbo_Block_Adminhtml_Form_Edit_Form extends Mage_Adminhtml_Block_Widget_Form
{
protected function _prepareForm()
{
$form = new Varien_Data_Form(
array(
'id' => 'edit_form',
'action' => $this->getUrl('*/*/test'),
'method' => 'post',
)
);
$form->setUseContainer(true);
$this->setForm($form);
$fieldset = $form->addFieldset('display', array(
'legend' => 'Display Settings',
'class' => 'fieldset-wide'
));
$fieldset->addField('label', 'text', array(
'name' => 'label',
'label' => 'Label',
));
if (Mage::registry('andybram_presbo')) {
$form->setValues(Mage::registry('andybram_presbo')->getData());
}
return parent::_prepareForm();
}
}
then in my controller i have 2 functions like below . . .
public function indexAction()
{
$this->loadLayout();
$this->_addContent($this->getLayout()->createBlock('presbo/adminhtml_form_edit_form'));
}
public function testAction()
{
echo 'form data here';
$this->loadLayout();
$this->renderLayout();
}
the form is displayed successfully but there is no button to send or say 'do an action'
Further Edit
i have successfully added a submit button to the form that successfully goes to the testAction and echo' "form data here".
how do i then access the data,
iv added the below line
$postData = $this->getRequest()->getPost();
now if i echo $postData, it just puts "array"
if i echo $postData[0] it doesnt put anything just a blank page
any ideas or pointers?
Magento is built as an MVC framework, thus you're right - you need to pass data from controller to the model, and do not do any DB updates directly in a controller's code. The best source for an example is the own Magento code - you can take any controller action, which saves data to DB to see, how it is done. E.g. check app/code/core/Mage/Adminhtml/controllers/NotificationController.php method markAsReadAction().
There you can see, that:
Data is retrieved from the request by calling
$this->getRequest()->getParam('id') - actually this is the answer
to the question, how to get the submitted data
Data is set to model, and then saved to the DB via call to the
$model->setIsRead(1)->save()
It is strongly encouraged to follow the same approach of working with models. This makes codes much better and easier to support.
Note, that "M" letter of "MVC" architecture in Magento is represented by two layers: Models and Resource Models.
Models:
Contain business logic of an entity. E.g. adding ten items to a
Shopping Cart model triggers a discount rule
Represented by classes with a general name of <Your_Module>_Model_<Model_Name>
If need to work with DB, then extend Mage_Core_Model_Abstract and have a Resource
Model, which is responsible for DB communication
Do not need to have basic save/load methods to be implemented, as the ancestor
Mage_Core_Model_Abstract already has all that routines ready to use
Created via call to Mage::getModel('<your_module>/<model_name>')
Resource Models:
Serve as DB abstraction layer, thus save/load data from DB, perform
other DB queries
Extend Mage_Core_Model_Resource_Db_Abstract in order to communicate with DB
Represented by classes with a general name of
<Your_Module>_Model_Resource_<Model_Name>
Automatically created by a corresponding model, when it needs to communicate with DB
So, in a controller you care about creating Models only. Resource Models are created by a Model automatically.
And, according to everything said above, your controller should look like:
public function testAction()
{
$model = Mage::getModel('your_module/your_model');
$model->setName('andy');
$model->save();
}
You can download a fully working example of the thing you need here.
There can be several variations to the code provided, depending on your specific case. But it represents a general approach to implementing the thing you want.

CakePHP validation not working for contact form

I am trying to do some very simple validation in my CakePHP contact form, but validation does not work eventhough I think I did everything necessary. Here's what I did:
I made a model like this:
class Office extends AppModel
{
var $name = 'Office';
var $useTable = false;
public $validate = array('onderwerp' => 'notEmpty');
}
(I also tried many other values for $validate from the CakePHP online manual)
In Config/bootstrap.php I made this rule for not letting CakePHP expect plural "Offices":
Inflector::rules('plural', array('rules' => array(),
'irregular' => array(),
'uninflected' => array('office')));
In OfficeController, I do this in my method contact():
$this->Office->set($this->request->data);
if($this->Office->validates()){
echo "code validates";
} else {
print_r($this->Office->validationErrors);
}
And in my Office/contact.ctp view, I have (amongst other code like starting and ending the form) this code:
$this->Form->input('onderwerp', array('label'=>false, 'size' => 60));
Now, even when I fill in the form, leaving empty the field 'onderwerp', it executes the code that should be executed when the code is executed.
When I print_r($this->request->data) or print_r($this->Office) I see that my onderwerp field is there and that it is empty (or filled when I do fill in something).
Now, when I add a public function validates() in my model and echo something there, it IS being displayed. So I'd say CakePHP knows where to find my model, and does execute my controller code. I also tried adding return parent::validates(); in my validates() function, but this also yielded no validation error, or any other error for that matter. My debug level is set to 2.
I guess I'm missing a needle in this haystack. Thanks for helping me finding it!
so drop all the inflector stuff.
and use the right model in your Form->create()
either
$this->Form->create(null)
or
$this->Form->create('Office');
and if you follow my advice to use a table less model with schema you will also have more power over input creation and validation.

Accessing data in kohana validation

i'll try and be as clear as possible.
I'm working on some form validation using the wonderful kohana framework. However i have come at a crossroads and not sure whether the way i have taken is a wise choice.
Basically, i have a date selector using several select boxes (i toyed with the idea of using javascript date pickers but the select boxes proved to be more suitable for my purpose) and a date field in a database. I wanted to concatenate these select boxes into the date field so it can be checked to make sure its valid.
protected $_rules = array(
'mydate' => array(
'not_empty' => NULL,
'date' => NULL,
),
);
Now to me, it makes most sense to include the validation in the model, since that's where the data layer is in the MVC pattern, so i decided to create some class attributes named $_rules, $_filters and $_callbacks, each set as protected and with my basic rules applied. And then a function in the model that sets up a validation object using these attributes and returning it to whatever controller is calling it, then the controller can just run the validation and the job is done.
My problem comes when i want to concat these select boxes, to me it makes most sense to make a custom filter and pass in the post data, but with the filters rules and callbacks being attributes, i can't add any variables to them. My current solution is to manually add the extra filter in when the validation setup function is being run something similar to this:
public function setupValid($post) {
$this->_filters['mydatefield'] = array(
'MyClass::MyConcat' => array($post);
);
//creates a validation object and adds all the filters rules and callbacks
}
But i don't feel this is the cleanest solution, i'm probably nit picking as the solution works the way i require it to. However i'm not sure whether a filter was ever intended to do such a thing as this, or whether this should be a callback as the callback has access to the array by default, but then again callbacks are called last, which would mean i couldn't apply any rules like, 'not_empty' (not important in this case since they are pre populated select boxes, but might be in another case)
So i guess my question is, am i using filters as they were intended to be used?
I hope i've managed to explain this clearly.
Thanks
you need to keep in mind that you should only validate fields inside the $_rules that are very important to your database or business logic.
so for example if you would try to setup other form somewhere else in your app or you would provide a restfull api for your app, validation of the field 'day_field_(that_doesnt_exists_in_the_database_and_is_used_to_privide_a_better_ux_in_this_one_form)' => array('not_empty' => NULL) will give you a hard time to do that.
so i suggest you to keep your $_rules like they are now and provide some logic to your values() method:
// MODEL:
public function values($values)
{
if ( ! empty($values['day']) && ! empty($values['month']) && ! empty($values['year']))
{
$values['mydate'] = $values['year'].'-'.$values['month'].'-'.$values['day'];
}
return parent::values($values);
}
// CONTROLLER:
if ($orm->values($form['form_array'])->check())
{
$orm->save();
}
else
{
$this->template->errors = $orm->validate()->errors('validation');
}

Resources