Creating extra field 'images' resource forms usually throws 'column not found' type database level error.
But I need that type of extra field on the resource forms for some business logic under the hood when the create/update form is submitted.
I tried using removeNonCreationFields method on resource to remove that field column from saving to database but does not work and still throws error.
Please note that ->hideWhenCreating() or ->readonly() is not relevant as I need to interact on that field on create/delete forms.
Is there any other way to make such situation success with extra fields? Please help. Thanks.
My solution was:
app/Nova/Post.php
/**
* #param NovaRequest $request
* #param \Illuminate\Database\Eloquent\Model $model
* #param \Illuminate\Support\Collection $fields
* #return array|void
*/
protected static function fillFields(NovaRequest $request, $model, $fields)
{
$fillFields = parent::fillFields($request, $model, $fields);
// first element should be model object
$modelObject = $fillFields[0];
// remove all extra non-database attributes from the model
unset($modelObject->to_profile_gallery);
// I am not sure it will work if we unset on $model parameter and return it
// But you should try first doing so on $model parameter and return $model
return $fillFields;
}
Then you should use two functions, one for how to save in database and another for how to retrieve that specific data from database. Use these on the extra Field.
->fillUsing(function($request, $model, $attribute, $requestAttribute){
// during creation photos are handled by Nova Resource Observer
if($model->type !== post_type_photo()) return;
// run only for update request
FilepondHelper::handleMediaFillUsingCallback(PostMediaTag::photos, true, $request, $model, $attribute, $requestAttribute); // only update
})
->resolveUsing(function($value, $resource, $attribute) use($request){
return FilepondHelper::handleMediaResolveUsingCallback(PostMediaTag::photos, $value, $resource, $attribute, $request);
}),
Hoping this will solve your issue. Thanks
Hasnat approach worked perfectly for my case.
I wanted to have one special field when creating a resource only for internal logic, which should be ignored/discarded and not related in any way to a database field.
Thanks!
Related
I am working on this laravel project where user can upload an avatar image. My users table does not have any column yet to store the file location. So i was testing this in phpunit following the TDD series in laracast.
After the file is uploaded successfully and moved to the desired location in the server, i called the update method on the authenticated user like below:
$user = auth()->user();
$user->update(['avatar_location' => 'avatars/avatar.jpg']);
Note that avatar_location is not yet there on the users table. I expected this to fail but it didn't. I tried to find out what was going on so i followed through to the update() method in the model class:
//file Illuminate/Database/Eloquent/Model.php
public function update(array $attributes = [], array $options = [])
{
//dd($attributes); //prints 'avatar_location"=>"avatars/avatar.jpg'
if (! $this->exists) {
//dd($attributes);
return false;
}
return $this->fill($attributes)->save($options);
}
till this point the dd($attribute) prints the value that i passed to the update() method.
So i followed into the fill() method that is being called with the attribute parameter. However when i die dumped the received parameter from inside the fill() method i am not seeing the key=>value pair that i passed. Instead it was showing the other attributes of the user:
/**
* Fill the model with an array of attributes.
*
* #param array $attributes
* #return $this
*
* #throws \Illuminate\Database\Eloquent\MassAssignmentException
*/
public function fill(array $attributes)
{
//dd($attributes);
//does not print 'avatar_location"=>"avatars/avatar.jpg'
//rather, prints:
//array:4 [
// "name" => "Armand Mraz"
// "email" => "akautzer#example.net"
// "password" => "$2y$10$h7OG9/Toh31MsyFQc8lfg.wHeQC7maP4Bh37bea.DXU//IuRuXZi."
// "remember_token" => "X0udISwEEM"
]
$totallyGuarded = $this->totallyGuarded();
foreach ($this->fillableFromArray($attributes) as $key => $value) {
$key = $this->removeTableFromKey($key);
// The developers may choose to place some attributes in the "fillable" array
// which means only those attributes may be set through mass assignment to
// the model, and all others will just get ignored for security reasons.
if ($this->isFillable($key)) {
$this->setAttribute($key, $value);
} elseif ($totallyGuarded) {
throw new MassAssignmentException($key);
}
}
return $this;
}
I spent a lot of time trying to figure out why?
can anyone please explain?
And why the update method is not failing even though i am trying to update a column that does not exist?
Thanks,Yeasir
When you're updating an object, Laravel is trying to match the keys of the array of data and the list of fillable fields. All pairs of key/valye missing from the fillable fields are not considered. It's the reason why it's not failing.
You have to update the fillable field list (property $fillable) in your user model.
Have a look at the documentation.
If you add avatar_location in your fillable fields list and the field doesn't exist, in this case, it will throw an exception.
I am having this error and none of the googled result i checked is similar to my problem.
I have an application with class Deal, User, and Matches
A deal has many matches.
A user has many matches.
A user has many deals.
I am attempting to create a new Match using my Deal object
$deal->matches()->create(['user_id'=>$id]);
This is my match class, i have defined all needed relationships
class Match extends Model
{
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $guarded = [];
public $timestamps = false;
public $expired_on = "";
public static function boot()
{
parent::boot();
static::creating(function ($model) {
$model->matched_on = $model->freshTimestamp();
});
}
public function __construct(){
$d = (new \DateTime($this->matched_on))->modify('+1 day');
$this->expired_on = $d->format('Y-m-d H:i:s');
}
/**
* Get the user that owns the match.
*/
public function user()
{
return $this->belongsTo('App\User');
}
/**
* Get the deal that owns the match.
*/
public function deal()
{
return $this->belongsTo('App\Deal');
}
}
And i keep getting this error when i attempt to create a new match.
QueryException in Connection.php line 647:
SQLSTATE[HY000]: General error: 1364 Field 'user_id' doesn't have a default value (SQL: insert into matches (deal_id) values (1))
I have my guarded to be an empty array, what could be the problem?
Remove the guarded array and add the fillable instead:
protected $fillable = ['user_id', 'deal_id'];
If you would like to revert to previous behavior, update your
config/database.php
file and set 'strict' => false for your connection.
Since it was a unique field in my case, I could not make it nullable.
For me, I had an empty constructor which was causing the issue don't know why.
Please comment if anyone knows the reason.
public function __construct(){
}
Commenting/removing it resolved the issue.
If you have a constructor in your model, just make sure it has a call to a parent constructor as well:
public function __construct( array $attributes = array() ) {
// mandatory
parent::__construct($attributes);
//..
}
Otherwise, it will break some functionality like Model::create.
Alexey Mezenin's Answer is correct and a good one.
Another way i used around it, for those who want to maintain the guarded empty array is to create a new Match object and put in the attributes and save.
$match->user_id = $id;
$match->deal_id = $deal->id;
$match->matched_on = $match->freshTimestamp();
$match->save();
I am using Laravel 8 and fixed this error thorugh this two steps:
move the word from $guarded array to $fillable array in User Mode
Config.database.php: 'strict' => false in the array of 'mysql'
Another way around this error is to include
'strict' => false,
into config/database.php within mysql array
When manually importing / exporting the databases, check if the transfer of all table settings was successful. If you forget to add an auto increment primary key, Laravel doesn't fill the value for you.
Adding the AUTO_INCREMENT afterwards will solve the problem.
I had this error but my wrong was making class model:
$book = new Book();
While this is true
$book = new Book($request->all());
changing your "config/database.php" won't help.
If you're getting this error, you're not sending the data to database correctly.
check your function in your controller, the create() method is probably being blocked by an if statement or something.
or
if it's an API, check the post request from the frontend that's where your issue is.
make sure the form is correctly passed into to request.
Currently I use next approach for retreiving data according to request:
/**
* #QueryParam(name="filters", nullable=true, map=true, description="Filter by fields. Must be an array ie. &filters[id]=3")
*/
public function cgetAction(ParamFetcherInterface $paramFetcher)
{
$filters = $paramFetcher->get('filters') ?: [];
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository($this->entityClassName())
->findBy($filters);
return $entities;
}
But I need something like this: specify complex conditions in GET request, for example
?filter={"where":{"or":[{"id":1},{"id":2},...,{"id":20"},{"id":21}]}}
?filter[where][date][gt]=2014-04-01T18:30:00.000Z
?filter={"where": {"keywords": {"inq": ["foo", "bar"]}}}
?filter[where][and][0][title]=My%20Post&filter[where][and][1][content]=Hello
etc
and get data from repository in according to this request.
Does exist any bundle for Symfony for this purpose? Will be glad for any advice.
Use the LexikFormFilterBundle, it's made for this use case, building form filters and then build a doctrine query from this form filter.
You'll find a complete example here.
I have this controller for a RESTful API I am building in Laravel Lumen which takes a relatively big amount of parameters and parses them into where queries, and data is fetched depending on if they were provided. For example,
GET /nodes?region=California
GET /nodes?ip=127.0.0.1
I am currently taking them in the constructor, building an array of the parameters (since I couldn't figure out how to get the raw get array in Lumen and it would be inconvenient because I already have other parameters there), and filtering out the null values (I am setting values to null if they are not in the query).
Now, when it comes to filtering the values each in the array, I am doing it by a foreach array. This is the cleanest way I could figure out to do it, without too much code (I don't want to make my controllers too fat.).
Is there any other way to do this cleanly, maybe with separation of functions/classes?
Here is my constructor code:
/**
* Get some values before using functions.
*
* #param Request $request Instance of request.
*/
public function __construct(Request $request)
{
$this->offset = (int) $request->input('offset', 0);
// TODO: I'm not sure how to implement this, code in question
$this->filters = [
'region' => $request->input('region', null),
'name' => $request->input('name', null),
'ip' => $request->input('ip', null)
];
$this->filters = array_filter($this->filters, function ($v) {
return !is_null($v);
});
// Set a sane SQL limit.
$this->limit = 5;
$this->request = $request;
}
And the controller code:
/**
* List all nodes.
*
* #return [string] [JSON containing list of nodes, if sorted.]
*/
public function all()
{
try {
// use filters provided
$data = Nodes::limit($this->limit)->offset($this->offset);
foreach ($this->filters as $filter => $value) {
$data->where($filter, $value);
}
$data = $data->get();
$response = $this->respond($data);
} catch (\Exception $e) {
$response = $this->respondServerError('Could not retrieve data from database.');
}
return $response;
}
So any time I have to do filtering of a resource-list in an API, here's how I do it.
First off though, before I begin, a quick tip concerning getting the Request object when you're in your controller method: If you add Request $request as a parameter for your all() function, you will have access to the $request variable there, same as your constructor. So the complete signature would be public function all(Request $request). Controller methods have the same magic dependency injection that other class constructors get in Laravel/Lumen. Alternatively, in your function you can always ask the app() function to give you an object of a specific class. Because the Request object is bound in the Container to just 'request', you can ask for the full class name, or just 'request': $request = app('request');
So once I have my request object, inside my controller method I like to go through each filter either as a group, or one-by-one, depending on how complex each filter is. Sometimes filters are complex, like a list of comma-separated IDs that need to be exploded into an array. If it's just simple string filters though, I tend to throw the list into an array and run through that.
Here's an example function to illustrate some ideas:
public function getIndex(Request $request)
{
//Create a User object to append WHERE clauses onto
$user = app('App\Models\User');
//Run through our simple text fields
foreach(['first_name', 'last_name', 'region', 'ip'] as $field) {
if ($request->has($field)) {
$user->where($field, $request->input($field));
}
}
//This field uses a LIKE match, handle it separately
if ($request->has('email')) {
$user->where('email', LIKE, '%' . $request->input('email') . '%');
}
//This field is a list of IDs
if ($request->has('id')) {
$ids = explode(',', $request->input('id'));
$user->whereIn('id', $ids);
}
//Use pagination
$users = $user->paginate(25);
/**
* Continue with the rest of response formatting below here
*/
}
You'll notice I used the paginate function to limit my results. When building an API endpoint that lists resources, you're going to want to put in your headers (my preference) or the response body information on how to get the first, previous, next, and last page of results. The Pagination feature in Laravel makes that easy, as it can construct most of the links using the links() method.
Unfortunately, you need to tell it what filter parameters were passed in the request so it can make sure it adds those to the links it generates. Otherwise you'll get links back without your filters, which doesn't do the client very much good for paging.
So here's a more complete example of recording filter parameters so they can be appended onto pagination links:
public function getIndex(Request $request)
{
//Create a User object to append WHERE clauses onto
$user = app('App\Models\User');
//List of filters we found to append to links later
$appends = [];
//Run through our simple text fields
foreach(['first_name', 'last_name', 'region', 'ip'] as $field) {
if ($request->has($field)) {
$appends[$field] = $request->input($field);
$user->where($field, $request->input($field));
}
}
//This field uses a LIKE match, handle it separately
if ($request->has('email')) {
$appends['email'] = $request->input('email');
$user->where('email', LIKE, '%' . $request->input('email') . '%');
}
//This field is a list of IDs
if ($request->has('id')) {
$appends['id'] = $request->input('id');
$ids = explode(',', $request->input('id'));
$user->whereIn('id', $ids);
}
//Use pagination
$users = $user->paginate(25);
//Make sure we append our filter parameters onto the pagination object
$users->appends($appends);
//Now calling $users->links() will return the correct links with the right filter info
/**
* Continue with the rest of response formatting below here
*/
}
Pagination documentation can be found here: https://laravel.com/docs/5.2/pagination
For an example of how pagination linking can be awesomely done, check out Github's API documentation: https://developer.github.com/v3/#pagination
In the end it's not too far off from what you were doing, conceptually. The advantage here is that you move the code into the method that needs it, instead of having it run in your constructor every single time the controller is initialized, even if a different method will be called.
Hope that helps!
In my cake PHP application, I have a edit form where "email" field is readonly that means user can not update it.
NOw if I think according to security point of view, user can update the field by 'firebug' or some other browser plugins.
I am using $this->User->save($this->data) to save the updated data. By this function Email can be also be updated.
Do we have any way in cake php so that I can prevent this field to be update, like by passing here a argument or something like this?
You can simply remove the email field from $this->data:
unset($this->data['User']['email']);
$this->User->save($this->data);
You could do something like:
$dontUpdateField = array('email');
$this->Model->save(
$this->data,
true,
array_diff(array_keys($this->Model->schema()),$dontUpdateField)
);
If security is a concern, simply reject any data that has unexpected values. In cake you could do this, but it can be adapted for any framework/cms
/**
* Checks input array against array of expected values.
*
* Checks single dimension input array against array of expected values.
* For best results put this is in app_controller.
*
* #param array $data - 1 dimensional array of values received from untrusted source
* #param array $expected - list of expected fields
* #return boolean - true if all fields are expected, false if any field is unexpected.
*/
protected function _checkInput($data,$expected){
foreach(array_keys($data) as $key){
if (!in_array($key,$expected)){
return;
}
}
return true;
}
/**
* edit method.
*
* put this in <Model>_controller
* #param string $id
* #return void
* #todo create errors controller to handle incorrect requests
* #todo configure htaccess and Config/routes.php to redirect errors to errors controller
* #todo setup log functionality to record hack attempts
* #todo populate $expected with fields relevant to current model
*/
function edit($id=null){
$expected = ('expectedVal1', 'expectedVal2');
$this->Model->id = $id;
if (!$this->Model->exists()) {
throw new NotFoundException(__('Invalid model'));
}
if ($this->request->is('post')) {
if (!$this->_checkData($this->request->data['Model'], $expected)) {
//log the ip address and time
//redirect to somewhere safe
$this->redirect(array('controller'=>'errors','action'=>'view', 405);
}
if ($this->Model->save($this->request->data)) {
//do post save routines
//redirect as necessary
}
else {
$this->Session->setFlash(__('The model could not be saved. Please, try again.'));
}
}
$this->set('model',$this->Model->read($expected,$id));
}
You can use the security component and make the email hidden. While using this component, hidden fields cant be changed or cake will blackhole the form.
http://book.cakephp.org/1.3/en/view/1296/Security-Component
If your application is public it is strongly recommended that you use security, otherwise it is kinda trivial to inject data in your models by submitting extra fields on the form and when you do $this->Model->save($this->data)) the extra fields are saved, unless you do the extra work of validating every field of $this->data;