I have a car with two columns: user_id and token
I would like to only pass the user_id on creation and create a token automatically:
$car = Car::create([
'user_id' => $user->id,
]);
this is my car class:
class Car extends Model
{
protected $guarded = [];
public function __construct()
{
parent::__construct();
$this->token = mb_substr(bin2hex(openssl_random_pseudo_bytes(32)),0,8);
}
When I create a car, the token field is correctly inserted. However the user_id field is null.
When I remove the __construct() method, then the user_id is correctly inserted (but then there is ofc no token).
I don't understand why the assignment in the constructor removes the user_id.
Any suggestions?
Rather than creating the token in your constructor, you could take advantage of Laravel's model events. In short, this allows you to listen for an event (e.g. "created," "updated," etc.), and perform an action on that event. If you replace your constructor with the following, it should solve the issue:
public static function boot()
{
self::created(function ($model) {
$model->update([
'token' = mb_substr(bin2hex(openssl_random_pseudo_bytes(32)),0,8);
]);
});
// If you're using the SoftDeletes trait, uncomment this line.
// static::bootSoftDeletes();
}
You'll create an instance of your Car model in your controller, and then the model event will update that instance with your token.
As an aside: since the token is generated randomly, and seems not to rely on any other data/functions, I don't believe there's any shame in dropping this line:
'token' = mb_substr(bin2hex(openssl_random_pseudo_bytes(32)),0,8);
Into the create method in your controller. Based on what you've provided, it would be the simplest way to solve for what you need.
The problem is your constructor doesn't have the correct method signature.
The create method in the laravel model creates a new model: $model = new static($attributes); The $attributes array is what sets the data on your fresh model. You need to make sure your constructor takes the attributes argument and passes it to the parent:
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
$this->token = mb_substr(bin2hex(openssl_random_pseudo_bytes(32)),0,8);
}
Related
I have a model in laravel and I want to do something after the first time which an object of my model is created. the simplest way is to add a static boot method inside my model's class like the code below:
class modelName extends Model
{
public static function boot()
{
parent::boot();
self::created(function ($model) {
//the model created for the first time and saved
//do something
//code here
});
}
}
so far so good! the problem is: the ONLY parameter that created method accepts is the model object itself(according to the documentation) :
Each of these methods receives the model as their only argument.
https://laravel.com/docs/5.5/eloquent#events
I need more arguments to work with after model creation. how can I do that?
Or is there any other way to do something while it's guaranteed that the model has been created?
laravel version is 5.5.
You're close. What I would probably do would be to dispatch an event right after you actually create the model in your controller. Something like this.
class WhateverController
{
public function create()
{
$model = Whatever::create($request->all());
$anotherModel = Another::findOrFail($request->another_id);
if (!$model) {
// The model was not created.
return response()->json(null, 500);
}
event(new WhateverEvent($model, $anotherModel));
}
}
I solved the issue using static property in eloquent model class:
class modelName extends Model
{
public static $extraArguments;
public function __construct(array $attributes = [],$data = [])
{
parent::__construct($attributes);
self::$extraArguments = $data ;
public static function boot()
{
parent::boot();
self::created(function ($model) {
//the model created for the first time and saved
//do something
//code here
self::$extraArguments; // is available in here
});
}
}
It works! but I don't know if it may cause any other misbehavior in the application.
Using laravel events is also a better and cleaner way to do that in SOME cases.but the problem with event solution is you can't know if the model has been created for sure and it's time to call the event or it's still in creating status ( and not created status).
I have a Model like this:
class Foo extends Model
{
protected $table = 'legacy_foo_table';
protected $primaryKey = 'cod_foo';
}
In my use case I must not delete, nor insert nor update the data in the database. I would like that my Model blocked these operations in the application. I can't change the table to be read-only, create a view nor change the database connection to be read-only.
So, how do I forbid changes in the database, in my software, without changing the database, while keeping Eloquent's queries and Object-Relational mapping? Even better if, when a change is attempted, an exception is thrown.
You said you can't change anything on the table itself, but can you create a new user that has read only access to that table? If possible that would be the best route. Otherwise you can update the model to restrict those methods.
You can override the update save and delete methods to throw an exception. On your model add:
public function update(array $attributes = [], array $options = [])
{
throw new \Exception('Updates are not allowed');
}
public function save(array $options = [])
{
throw new \Exception('Updates are not allowed');
}
public function delete()
{
throw new \Exception('Deleting is not allowed');
}
This will stop you from doing these methods on an instance of the model so doing stuff like this will result in an exception.
$model->update(['attribute' => 'new value']);
$model->delete();
$model->attribute = 'new value';
$model->save();
It will not prevent you from updating the table with the query builder. So you could still do this:
Foo::where('condition', '=', 'test')->update(['attribute' => 'new value']);
Blocking this would require you to implement a custom query builder which would take a lot more effort.
you can create trait like this example
class User extends Model {
use ReadOnlyTrait;
}
Before anyone asks, I've looked into CRUD generators and I know all about the Laravel Resource routes, but that's not exactly what I'm pulling for here.
What I'm looking to do is create one Route with a couple parameters, and one global class that (uses/extends?) the Model controller for simple CRUD operations. We have 20 or so Models and creating a Resource Controller for each table would be more time consuming than finding a way to create a global CRUD class to handle all "api" type calls and any ajax json request like a create / update / destroy statement.
So my question is what is the cleanest and best way to structure a class to handle all CRUD requests for every Model we have without having to have a resource controller for every model? I've tried researching this and can't seem to find any links except ones to CRUD generators and links describing the laravel Resource route.
The easiest way would be to do the following:
Add a route for your resource controller:
Route::resource('crud', 'CrudController', array('except' => array('create', 'edit')));
Create your crud controller
<?php namespace App\Http\Controllers;
use Illuminate\Routing\Controller;
use App\Models\User;
use App\Models\Product;
use Input;
class CrudController extends Controller
{
const MODEL_KEY = 'model';
protected $modelsMapping = [
'user' => User::class,
'product' => Product::class
];
protected function getModel() {
$modelKey = Input::get(static::MODEL_KEY);
if (array_key_exists($modelKey, $this->modelsMapping)) {
return $this->modelsMapping[$modelKey];
}
throw new \InvalidArgumentException('Invalid model');
}
public function index()
{
$model = $this->getModel();
return $model::all();
}
public function store()
{
$model = $this->getModel();
return $model::create(array_except(Input::all(), static::MODEL_KEY));
}
public function show($id)
{
$model = $this->getModel();
return $model::findOrFail($id);
}
public function update($id)
{
$model = $this->getModel();
$object = $model::findOrFail($id);
return $object->update(array_except(Input::all(), static::MODEL_KEY));
}
public function destroy($id)
{
$model = $this->getModel();
return $model::remove($id);
}
}
Use your new controller :) You have to pass the model parameter that will contain the model key - it must be one of the allowed models in the whitelist. E.g. if you want to get a User with id=5 do
GET /crud/5?model=user
Please keep in mind that it's as simple as possible, you might need to make the code more sophisticated to match your needs.
Please also keep in mind that this code has not been tested - let me know if you see any typos or have some other issues. I'll be more than happy to get it running for you.
Unless you want to implement CRUD manually, consider to integrate a ready-made datagrid such as phpGrid.
Check out integration walkthrough: http://phpgrid.com/example/phpgrid-laravel-5-twitter-bootstrap-3-integration/ No models are required and the code is minimum. It can almost do anything.
A basic working CRUD:
// in a controller
public function index()
{
$dg = new \C_DataGrid("SELECT * FROM orders", "orderNumber", "orders");
$dg->enable_edit("FORM", "CRUD");
$dg->display(false);
$grid = $dg -> get_display(true);
return view('dashboard', ['grid' => $grid]);
}
You need one generic class for all CRUD operations and there are many ways to achieve that and one rule for all may not fit but you may try the approach that I'm going to describe now. This is an abstract idea, you need to implement it, so at first, think the URI for all CRUD operations. In this case you must follow a convention and it could be something like this:
example.com/user/{id?} // get all or one by id (if id is available in the URI)
example.com/user/create // Show an empty form
example.com/user/edit/10 // Show a form populated with User model
example.com/user/save // Create a new User
example.com/user/save/10 // Update an existing User
example.com/user/delete/10 // Delete an existing User
In ths case the user could be something else to specify the name of the model for example, example.com/product/create and keeping that on mind, you need to declare routes as given below:
Route::get('/{model}/{id?}', 'CrudController#read');
Route::get('/{model}/create', 'CrudController#create');
Route::get('/{model}/edit/{id}', 'CrudController#edit');
Route::post('/{model}/save/{id?}', 'CrudController#save');
Route::post('/{model}/delete/{id}', 'CrudController#delete');
Now, in your app\Providers\RouteServiceProvider.php file modify the boot method and make it look like this:
public function boot(Router $router)
{
$model = null;
$router->bind('model', function($modelName) use (&$model, &$router)
{
$model = app('\App\User\\'.ucfirst($modelName));
if($model)
{
if($id = $router->input('id'))
{
$model = $model->find($id);
}
return $model ?: abort(404);
}
});
parent::boot($router);
}
Then declare your CrudController as given below:
class CrudController extends Controller
{
protected $request = null;
public function __construct(Request $request)
{
$this->request = $request;
}
public function read($model)
{
return $model->exists ? $model : $model->all();
}
// Show either an empty form or a form
// populated with the given model atts
public function createOrEdit($model)
{
$classNameArray = explode('\\', get_class($model));
$className = strtolower(array_pop($classNameArray));
$view = view($className . '.form');
$view->formAction = "$className/save";
if(is_object($model) && $model->exists)
{
$view->model = $model;
$view->formAction .= "/{$model->id}";
}
return $view;
}
public function save($model)
{
// Validation required so do it
// Make sure each Model has $fillable specified
return $this->model->fill($this->request)->save();
}
public function delete($model)
{
return $this->model->delete();
}
}
Since same form is used to creating and updating a model, use something like this to create a form:
<form action="{{url($formAction)}}" method="POST">
<input
type="text"
class="form-control"
name="first_name" value="{{old('first_name', #$model->first_name)}}"
/>
<input type="Submit" value="Submit" />
{!!csrf_field()!!}
</form>
Remember that, each form should be in a directory corresponding to the model, for user add/edit, form should be in views/user/form.blade.php and for product model use views/product/form.blade.php and so on.
This will work and don't forget to add validation before saving a model and validation could be done inside the model using model events or however you want. This is just an idea but probably not the best way to it.
I have a custom setter that I'm running in a __construct method on my model.
This is the property I'm wanting to set.
protected $directory;
My Constructor
public function __construct()
{
$this->directory = $this->setDirectory();
}
The setter:
public function setDirectory()
{
if(!is_null($this->student_id)){
return $this->student_id;
}else{
return 'applicant_' . $this->applicant_id;
}
}
My problem is that inside my setter the, $this->student_id (which is an attribute of the model being pulled from the database) is returning null.
When I dd($this) from inside my setter, I notice that my #attributes:[] is an empty array. So, a model's attributes aren't set until after __construct() is fired. How can I set my $directory attribute in my construct method?
You need to change your constructor to:
public function __construct(array $attributes = array())
{
parent::__construct($attributes);
$this->directory = $this->setDirectory();
}
The first line (parent::__construct()) will run the Eloquent Model's own construct method before your code runs, which will set up all the attributes for you. Also the change to the constructor's method signature is to continue supporting the usage that Laravel expects: $model = new Post(['id' => 5, 'title' => 'My Post']);
The rule of thumb really is to always remember, when extending a class, to check that you're not overriding an existing method so that it no longer runs (this is especially important with the magic __construct, __get, etc. methods). You can check the source of the original file to see if it includes the method you're defining.
I wouldn't ever use a constructor in eloquent. Eloquent has ways to accomplished what you want. I would used a boot method with an event listener. It would look something like this.
protected static function boot()
{
parent::boot();
static::retrieved(function($model){
$model->directory = $model->student_id ?? 'applicant_' . $model->applicant_id;
});
}
Here are all the model events you can use: retrieved, creating, created, updating, updated, saving, saved, deleting, deleted, trashed, forceDeleted, restoring, restored, and replicating.
I am trying to check in the constructor of a model if the currently authenticated user is allowed to access the given model, but I am finding that $this from the constructor's context is empty. Where are the attributes assigned to a model in Laravel and how should I go about calling a method once all of the attributes have been loaded?
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
var_dump($this); // empty model
$this->checkAccessible();
}
Cheers in advance
As stated in the other answers & comments, there are better ways to achieve the aims of the question (at least in modern Laravel). I would refer in this case to the Authorization chapter of the documentation that goes through both gates and policies.
However, to answer the specific question of how to call a method once a models attributes have been loaded - you can listen for the Eloquent retrieved event. The simplest way to do this within a class is using a closure within the class booted() method.
protected static function booted()
{
static::retrieved(function ($model) {
$model->yourMethod() //called once all attributes are loaded
});
}
You can also listen for these events in the normal way, using listeners. See the documentation for Eloquent events.
you can use controller filter to check whether user logged in or not and than you call any model function.
public function __construct(array $attributes = []){
$this->beforeFilter('auth', array('except' => 'login')); //login route
if(Auth::user()){
$user_id = Auth::user()->user_id;
$model = new Model($attributes);
//$model = User::find($user_id);
}
}
Binding Attributes to Model from constructor
Model.php
public function __construct(array $attributes = array())
{
$this->setRawAttributes($attributes, true);
parent::__construct($attributes);
}
As it was mentioned by Rory, the retrieved event is responsible for that.
Also, it could be formed in a much cleaner and OOP way with Event/Listener approach, especially if you need to write a lot of code or have few handlers.
As it described here, you can just create an event for the Model like
protected $dispatchesEvents = [
'retrieved' => UserLoaded::class,
];
You need to create this class, eloquent event accepts the model by default:
class UserLoaded
{
protected User $user;
public function __construct(User $user)
{
$this->user = $user;
}
}
Then here is described how to declare listener for this event. It should be somewhere in the EventListenerProvider like this:
protected $listen = [
UserLoaded::class => [
UserLoadedListener::class
],
];
The listener should just implement method handle() (check article) like:
public function handle(UserLoaded $event)
{
// your code
}
Another possibility is to register model Observer, as it´s described here