I want to store books, with title, year and already existing Author. An author is saved in a table authors that have a one to many relationship with books table. To create a book i have a from with two text inputs and one select. Select is filled from database.
Now i want to store them and attach the author immediately.
I can't pass the author in the route because it's dynamically due the select-input.
Is there a possibility to do it like the call below?
Route:
Route::post('/store', [BookController::class, 'store'])->name('book.store');
Controller:
public function store(Request $request,Author $author_id)
{
$validated = $request->validate([
'title' => 'required',
'year' => 'required',
'book_id' => 'required'
]);
Book::create($request->all());
return redirect()->route('book.index');
}
If I understand correctly, you want to use route model binding for your Author, but that value is chosen with a <select> on the form.
Here's a solution to do that; if you don't have to use route model binding, it is much simpler, see the second solution below.
Route model binding solution
For route model binding, you'll need a route like this:
Route::post('/store/{author}', [BookController::class, 'store'])->name('book.store');
So you need to know the author ID to generate the form action URI - which you can't do at page load time because the user has not chosen an author yet. One way to solve this is with Javascript - every time the select chages, find the selected author, and update the form's action.
Here's a working HTML/JS snippet which does that, though you'll need to look at the form in your browser's developer tools to see the action changing.
let form = document.querySelector('form');
let defaultAction = form.dataset.action;
let select = document.querySelector('select');
let debugDisplay = document.querySelector('p');
let action;
select.addEventListener('change', () => {
action = defaultAction + select.value;
form.setAttribute('action', action);
debugDisplay.innerHTML = 'Form action is now "' + action + '"';
});
<form action="" method="post" data-action="/store/">
<select name="author_id">
<option>Choose an author</option>
<option value="1">William Gibson</option>
<option value="2">Iain M Banks</option>
</select>
</form>
<p></p>
And now, assuming you use the route, form, and Javascript shown above, and that you have your Author <-> Book relationships set up, your controller can do:
public function store (Request $request, Author $author) {
// Your validation ...
// Create the book for this author
$book = $author->books()->create($request->all());
return redirect()->route('book.index');
}
Note you should probably include some kind of front end Javascript validation that checks if an author is selected, otherwise the action is not set and the form will post to the current URL, which will probably result in a 404.
Simpler solution
If you can live without route model binding, this is trivially simple. Your existing route is fine:
Route::post('/store', [BookController::class, 'store'])->name('book.store');
Your form should include the author_id, exactly as in the example above.
And the controller just needs 1 extra line to find the selected author:
public function store (Request $request) {
// Your validation ...
// Find the author
$author = Author::find($request->author_id);
// Create the book for this author
$book = $author->books()->create($request->all());
return redirect()->route('book.index');
}
Second parameter must be sent in order to get Model from authors table.
Unless you modify your route to accept params and add JS listener to form on submit, change action URL with selected author, it is not possible per my knowledge..
Since you are not doing anything with Author model, maybe you can just add validation for author_id => "required,exists:authors" ?
Related
I have a Laravel 6 app and am trying to pass two parameters from my view's form to my controller via a resource route. I can pass one, no problem, but passing two gives the same error:
Too few arguments to function App\Http\Controllers\Admin\SubscriptionsController::update(), 1 passed and exactly 2 expected
I've tried many different arrangements suggested from other posts but none bring the desired result.
Here's my route:
Route::resource('subscriptions', 'Admin\SubscriptionsController')
Here's my form in my view:
{{ Form::open(['route' => ['admin.subscriptions.update', $plan->id, $coupon_code], 'method' => 'PUT', 'id' => 'role-' . $plan->id, $coupon_code]) }}
Coupon Code: <input type="text" name="coupon_code">
{{ Form::close() }}
Here's my controller. It doesn't reach the dd() test.
public function update($id, $coupon_code)
{
dd($coupon_code);
...
In the error Whoops! page, I can see the POST DATA that $coupon_code is being sent over.
However, if I remove the $coupon_code parameter from the controller (leaving public function update($id) ) it functions fine passing $id from form to controller, but I don't get the $coupon_code data I need to process. Adding the second parameter bombs it.
Any suggestions are very welcome.
As you are using the resource controller, it won't allow you to pass additional fields directly to its update() route. Instead, you have to override the update route.
Here is how you can do it, Immediately below your resource.
You can add a new route as below:
// web.php
Route::resource('subscriptions', 'Admin\SubscriptionsController')
// Make sure you add name when you are overriding the route
Route::put('subscriptions/{subscription}/{coupon_code?}', 'Admin\SubscriptionsController#update'])->name('admin.subscriptions.update');
I believe you won't be sending the coupon code every time so you can add {coupon_code?} which becomes optional.
In you controller's update() method, make the coupon_code optional
public function update($id, $coupon_code = null)
{
...
}
You can not add a new param for Route::resource. If you really want to take 2 params, you should create a new route.
for an example:
Route::resource('subscriptions', 'Admin\SubscriptionsController')->except('update');
Route::put('/subscriptions/{id}/{coupon_code}', 'Admin\SubscriptionsController#update')->name('subscriptions.update');
But I think it's better not using method params. Why not just using input form?
so we can process the coupon code like this:
request()->coupon_code;
I have a Company, where I like to validate the update request. Until now I validated the request inside the update() action but I like to move this to its own CompanyUpdateRequest.
In the validation I check of the uniqueness of the tax number but of course I like to allow the same tax number for the very company.
'tax_number' => [
'required',
'string',
Rule::unique('companies')->ignore($company->tax_number),
],
This works as long it is placed inside the action, where I have $company already:
public function update(Request $request, Company $company)
{
}
My question is now, how I get the company inside the CompanyUpdateRequest?
I know that I could put the ID of the company inside a hidden field in the form, send it along with the request, pull the company from DB ... but this feels kind of wrong. Does anybody have a better / another approach or idea?
Thanks a lot.
use route() method. Assume your route parameter name is company-
$this->route('company');
Note: parameter method inside route method needs to exactly same as url route parameter. In this case-
Route::post('yourUrl/{company}','SomeController#method');
You can pass any data through a form:
<input name="company_id" value="{{ $company->tax_number }}" type="hidden">
Then in the CompanyUpdateRequest class:
Rule::unique('companies')->ignore($request->company_id),
You can also change this rule to:
'companies' => 'unique:companies,company_id,' . $request->company_id,
You can get it with $this->route('paramName');
Rule::unique('companies')->ignore($this->route('company')),
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 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.
The problem
I have a login form available on every page (in the right menu). The problem is that when the user is on the register page, the fields from the login form are validated. I have username and password fields in both forms, and both are validated.
Ideas:
Different field names for registration form (register_username, register_password, register_email) and then set normal names before saving.
Different model (but albo using the users table) for login form?
Anyway, I just wonder what is the best way to solve this.
I'm guessing that both forms would submit to different actions, with the registration form submitting to Users->register() and the login form submitting to Users->login().
I would suggest that when you're in the register() action, you could try copying the relevant variable into another associative array and then validating and saving that, rather than validating and saving the $this->data variable.
Your second option is correct. I had encountered this problem before and wrote an article about it at the Bakery: Multiple Forms per page for the same model
The basic idea is to create separate models for each form extending the original model:
class RegisterForm extends User {
}
Then, load these forms in your controller however you please:
$this->loadModel('RegisterForm');
Then call validation as usual:
$this->RegisterForm->save($this->data);
For your particular case, you might not want to create the LoginForm model, and have only the RegisterForm model. This will let you take advantage of whatever magic the AuthComponent has.
HTH.
I haven't tried two forms with the same inputs, but this works for two forms with different inputs. I don't see why it shouldn't work for your needs.
View:
Make sure each submit button has a name value so that $this->params can identify it.
//first form ...
<?php
$profile_options = array('label' => 'edit profile',
'name' => 'form1');
echo $this->Form->end($profile_options);
?>
//second form ...
<?php
$password_options = array('label' => 'edit password',
'name' => 'form2');
echo $this->Form->end($password_options);
?>
Controller action:
Use $this->params to test for each form submission
if(isset($this->params['form']['form1'])){
$this->User->set($this->data); //necessary to specify validation rules
if($this->User->validates(array('fieldList' => array('email')))){
$this->User->saveField('email', $this->data['User']['email']);
}
}
elseif(isset($this->params['form']['form2'])){
//same deal for second form
}
It may be desirable to validate your model only using a subset of the validations specified in your model. For example say you had a User model with fields for first_name, last_name, email and password. In this instance when creating or editing a user you would want to validate all 4 field rules. Yet when a user logs in you would validate just email and password rules. To do this you can pass an options array specifying the fields to validate:
if ($this->User->validates(array('fieldList' => array('email', 'password')))) {
// valid
} else {
// invalid
}