I am new to Laravel and can't quite wrap my head around the Form Requests and how to use them. In simple cases, it's simple enough but what I need is a conditional creation of a related model before progressing with the rest of the request. Let me explain.
//Job model
job_id PK
client_id
some_field
//Client model
client_id
external_id
name
Now in my Create Job interface, I have a Select2 combo box that uses AJAX to search 2 different sources and can produce 3 different results.
So, say I am creating new Job and can have a POST looking like any of these:
Script found a Client with id 21, we just want to save, it all is simple
'client_id' => 21
'some_filed' => Whatever
OR
Script didn't find a Client but we did a search of external API and returned this Identifier, which I can then parse to create a new Client. This needs to be done before I save Job as I need to have a client_id ready for it
'client_id' => '196c3c7e34cde1d4593391ddf1901fd7'
'some_filed' => Whatever
OR
Script finds neither local Client nor a remote datapoint so we want to create a new Client using just the name provided. Of course this has to happen before saving the Job
'client_id' => 'My new client name'
'some_filed' => Whatever
Where do I perform all this functionality?
My first procedural guess would be to stick $data['client_id'] = Client::HandleClientAndReturnId($data['client_id']) in StoreJob's validationData() before returning the result.
But how would I handle/report possible validation issues with creating new Client and how to manage transaction - I don't want to create a Client if validation of Job fails.
I am using Form Requests and actually am parsing several models in the request already, but those are HAS_ONE type of relationships so I create them after the Job. Here I need to reverse the process. Here is what I have in my controller, if that is of any use:
class JobController extends Controller
{
public function store(StoreJob $request, StoreJobDataAdd $request_2, StoreJobDataSup $request_3)
{
$job = Job::create($request->validated());
if ($_POST['add_on']) {
$job->dataAdd()->create($request_2->validated());
}
if ($_POST['sup_on']) {
$job->dataSup()->create($request_3->validated());
}
return new JsonResponse([
'success' => true,
'message' => __('New Job has been created.')
]);
}
}
Here are my relations:
class Job extends Model
{
public function dataAdd()
{
return $this->hasOne(JobDataAdd::class, 'job_id', 'job_id');
}
public function dataSup()
{
return $this->hasOne(JobDataSup::class, 'job_id', 'job_id');
}
public function client()
{
return $this->belongsTo(Client::class, 'contact_id', 'contact_id_client');
}
}
I have a feeling I am missing the right way to do it but not quite sure where to look for the answers so please point me in the right direction.
Clarification:
The objective of the whole exercise is to allow users to create new Clients or select existing by using one combobox. It's a Select2 box. For unfamiliar - a dropdown replacement that has an ability to free-type a string that is used to search for existing records and if not found it sends to the server what user typed in. Something like a dynamic dropdown with ability to search and add options by the user.
Related
In my application, I have users that are connected to businesses. These businesses have job offers. When a user wants to edit a business' job offer, the url will be like /business/foo/job/bar. When I change the business variable to the name of a different one, like this: /business/other-business/job/bar I still get the job called bar even though it does not belong to the business. I tried to use gates to check if the job offers belong to the business but it didn't work. Below is the code for the edit function which shows the edit page.
public function edit(Business $business, Job $job)
{
return view('dashboard.business.jobs.edit', [
'business' => $business,
'job' => $job,
]);
}
I can add the following code to all the functions but it is not very pretty
if($business->id !== $job->business->id) { return abort(404); }
I was wondering if there is a better solution to this problem.
Well you can choose between
if($business->jobs->contains($job->id)) return redirect()->to("somewhere");
or
if($job->business->is($business)) return redirect()->to("somewhere");
The second one is more efficient because you have to retrive from the database just one record to check if they are correlated, the business record, where the first one instead you have to retrive all the jobs of that business.
Those solution in my opinion are actually very clear, you can literally read them and understand what you are doing.
Also if you want just one line of code, you can do this:
public function edit(Business $business, Job $job)
{
return
$job->business->is($business)
?
view('dashboard.business.jobs.edit', [
'business' => $business,
'job' => $job,
])
:
return redirect()->to("somewhere");
}
I need to set up JWT authentication for my Yii2 app. The authentication itself works fine, the token gets parsed and I can read it's data in my User model. But the problem is that I need to compare this data to a real user in my DB. So, I've got this method in the User model which extends ActiveRecord
public static function findIdentityByAccessToken($token, $type = null) {
$user = User::findOne(['ID' => 1]);
die(json_encode($user));
}
It's very simplified just to see that it finds a user. It does not and it always returns this:
{"id":null,"userLogin":null,"userPass":null,"userNicename":null,"userEmail":null,"userUrl":null,"userRegistered":null,"userActivationKey":null,"userStatus":null,"displayName":null}
The data is not populated. But if I do the same inside any controller, like so
class TokenController extends ActiveController
{
public $modelClass = 'app\models\User';
public function actionFind(){
return User::findOne(['ID' => 1]);
}
}
It works great and I get the User object populated with correct data.
Is it possible to get user from not within an ActiveController class?
Well, I don't know exactly what is wrong with this line here die(json_encode($user));
But it actually finds and populates the user and I can access it later via
Yii::$app->user->identity
so I can also blindly compare its ID and password to the real ones here
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.
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.
I'm using Cakephp and trying to put in a method to make sure our reservation system doesn't let two users book the same appointment. Ex. User 1 opens the appointment, and User 2 opens it simultaneously. User 1 books the appointment. User 2 tries to book it but the system checks and sees it is no longer available.
I imagine this would take place in validation, or in a beforeSave(), but can't figure out how to do it.
Right now I made a function in the model to call from the controller. In the controller I have:
if ($this->Timeslot->checkIfNotAvailable()) {
$this->Session->setFlash('This timeslot is no longer available');
$this->redirect(array('controller' => 'users', 'action' => 'partner_homepage'));
}
and in the model I have this function:
function checkIfNotAvailable($data) {
$this->recursive = -1;
$timeslot = $this->find('all', array(
'conditions' => array(
'Timeslot.id' => $this->data['Timeslot']['id'])
)
);
if ($timeslot['student_id'] == 0) {
//They can reserve it, do not spring a flag
return false;
} else {
//Throw a flag!
return true;
}
}
I think I'm mixed up using custom validation when it's not called for. And it's not working obviously. Any suggestions?
Thanks!
If what you have is working, you can stick with it, you could also try creating a beforeValidate() call back function in your Model.
class YourModel extends AppModel {
function beforeValidate(){
if( !$this->checkIfNotAvailable( $this->data ) ) {
unset($this->data['YourModel']['time_slot']);
}
return true; //this is required, otherwise validation will always fail
}
}
This way you remove the time_slot before it goes to validation and it will drop a validation error at that point, kicking the user back to the edit page and getting them to pick a different time slot, ideally the updated data entry page will no longer have the used time slot available.