Edit function:
public function editCheck($id, LanguagesRequest $request)
{
try{
$language = language::select()->find($id);
$language::update($request->except('_token'));
return redirect()->route('admin.languages')->with(['sucess' => 'edit done by sucsses']);
} catch(Exception $ex) {
return redirect()->route('admin.addlanguages');
}
}
and model or select function
public function scopeselect()
{
return DB::table('languages')->select('id', 'name', 'abbr', 'direction', 'locale', 'active')->get();
}
This code is very inefficient, you're selecting every record in the table, then filtering it to find your ID. This will be slow, and is entirely unnecessary. Neither are you using any of the Laravel features specifically designed to make this kind of thing easy.
Assuming you have a model named Language, if you use route model binding, thing are much simpler:
Make sure your route uses the word language as the placeholder, eg maybe your route for this method looks like:
Route::post('/languages/check/{language}', 'LanguagesController#editCheck');
Type hint the language as a parameter in the method:
public function editCheck(Language $language, LanguagesRequest $request) {
Done - $language is now the single model you were afer, you can use it without any selecting, filtering, finding - Laravel has done it all for you.
public function editCheck(Language $language, LanguagesRequest $request) {
// $language is now your model, ready to work with
$language::update($request->except('_token'));
// ... etc
If you can't use route model binding, or don't want to, you can still make this much simpler and more efficient. Again assuming you have a Language model:
public function editCheck($id, LanguagesRequest $request) {
$language = Language::find($id);
$language::update($request->except('_token'));
// ... etc
Delete the scopeselect() method, you should never be selecting every record in your table. Additionally the word select is surely a reserved word, trying to use a function named that is bound to cause problems.
scopeselect() is returning a Collection, which you're then trying to filter with ->find() which is a method on QueryBuilders.
You can instead filter with ->filter() or ->first() as suggested in this answer
$language = language::select()->first(function($item) use ($id) {
return $item->id == $id;
});
That being said, you should really find a different way to do all of this entirely. You should be using $id with Eloquent to get the object you're after in the first instance.
Related
I'm looking for a way to make a dynamic & global model filter in Laravel.
I'm imagining a function like the following in my User.php model:
public function filter() {
return ($someVariable === true);
}
Whenever I do a query using Eloquent's query builder, I only want users to show up in the collection when the filter above returns true. I would have thought a feature like that existed, but a quick look at the documentation suggests otherwise. Or did I miss it?
I believe what you're looking for is Query Scopes.
They are methods that may be defined in a global or local context, that mutate the current query for a given model.
https://laravel.com/docs/5.5/eloquent#query-scopes
For example:
Lets say I have a database table called "Teams" and it has a column on it called "Wins." If I wanted to retrieve all Teams that had a number of Wins between Aand B I could write the following Local scope method on the teams model:
public function scopeWinsBetween($query, int $min, int $max)
{
return $query->whereBetween('wins', $min, $max);
}
And it could be invoked as such:
$teams = Teams::winsBetween(50, 100)->get();
I think you could use Collection macro but you will need to suffix all your eloquent get(); to get()->userDynamicFilter();
Collection::macro('userDynamicFilter', function () {
//$expected = ...
return $this->filter(function ($value) use($expected) {
return $value == $expected;
});
});
Thanks. For now I've simply added a post filter option to the models using the following code:
// Apply a post filter on the model collection
$data = $data->filter(function($modelObject) {
return (method_exists($modelObject, 'postFilter')) ? $modelObject->postFilter($modelObject) : true;
});
in Illuminate/Database/Eloquent/Builder.php's get() function, after creating the collection. This allows me to add a function postFilter($model) into my model which returns either true or false.
Probably not the cleanest solution but a working one for now.
Is there any way to update a record in Laravel using eloquent models just if a change has been made to that record? I don't want any user requesting the database for no good reason over and over, just hitting the button to save changes. I have a javascript function that enables and disables the save button according with whether something has changed in the page, but I would like to know if it's possible to make sure to do this kind of feature on the server side too. I know I can accomplish it by myself (meaning: without appealing to an internal functionality of the framework) just by checking if the record has change, but before doing it that way, I would like to know if Laravel eloquent model already takes care of that, so I don't need to re-invent the wheel.
This is the way I use to update a record:
$product = Product::find($data["id"]);
$product->title = $data["title"];
$product->description = $data["description"];
$product->price = $data["price"];
//etc (string values were previously sanitized for xss attacks)
$product->save();
You're already doing it!
save() will check if something in the model has changed. If it hasn't it won't run a db query.
Here's the relevant part of code in Illuminate\Database\Eloquent\Model#performUpdate:
protected function performUpdate(Builder $query, array $options = [])
{
$dirty = $this->getDirty();
if (count($dirty) > 0)
{
// runs update query
}
return true;
}
The getDirty() method simply compares the current attributes with a copy saved in original when the model is created. This is done in the syncOriginal() method:
public function __construct(array $attributes = array())
{
$this->bootIfNotBooted();
$this->syncOriginal();
$this->fill($attributes);
}
public function syncOriginal()
{
$this->original = $this->attributes;
return $this;
}
If you want to check if the model is dirty just call isDirty():
if($product->isDirty()){
// changes have been made
}
Or if you want to check a certain attribute:
if($product->isDirty('price')){
// price has changed
}
You can use $product->getChanges() on Eloquent model even after persisting. Check docs here
I like to add this method, if you are using an edit form, you can use this code to save the changes in your update(Request $request, $id) function:
$post = Post::find($id);
$post->fill($request->input())->save();
keep in mind that you have to name your inputs with the same column name. The fill() function will do all the work for you :)
use only this:
Product::where('id', $id)->update($request->except(['_token', '_method']));
At times you need to compare the newly changed value with the previous one and if you are looking for that here is the solution.
if (
$obj->isDirty('some_field_name') &&
$obj->some_field_name != $obj->getOriginal('some_field_name')
) {
// Make required changes...
}
});
}
The reference of the derived solution is here.
Maybe Laravel has updated since, but wasChanged is working for me better than isDirty in all of these previous answers.
For example:
if($post->wasChanged('status') && $post->status == 'Ready') // Do thing
Whenever I add additional logic to Eloquent models, I end up having to make it a static method (i.e. less than ideal) in order to call it from the model's facade. I've tried searching a lot on how to do this the proper way and pretty much all results talk about creating methods that return portions of a Query Builder interface. I'm trying to figure out how to add methods that can return anything and be called using the model's facade.
For example, lets say I have a model called Car and want to get them all:
$cars = Car::all();
Great, except for now, let's say I want to sort the result into a multidimensional array by make so my result may look like this:
$cars = array(
'Ford' => array(
'F-150' => '...',
'Escape' => '...',
),
'Honda' => array(
'Accord' => '...',
'Civic' => '...',
),
);
Taking that theoretical example, I am tempted to create a method that can be called like:
$cars = Car::getAllSortedByMake();
For a moment, lets forget the terrible method name and the fact that it is tightly coupled to the data structure. If I make a method like this in the model:
public function getAllSortedByMake()
{
// Process and return resulting array
return array('...');
}
And finally call it in my controller, I will get this Exception thrown:
Non-static method Car::getAllSortedByMake() should not be called statically, assuming $this from incompatible context
TL;DR: How can I add custom functionality that makes sense to be in the model without making it a static method and call it using the model's facade?
Edit:
This is a theoretical example. Perhaps a rephrase of the question would make more sense. Why are certain non-static methods such as all() or which() available on the facade of an Eloquent model, but not additional methods added into the model? This means that the __call magic method is being used, but how can I make it recognize my own functions in the model?
Probably a better example over the "sorting" is if I needed to run an calculation or algorithm on a piece of data:
$validSPG = Chemical::isValidSpecificGravity(-1.43);
To me, it makes sense for something like that to be in the model as it is domain specific.
My question is at more of a fundamental level such as why is all()
accessible via the facade?
If you look at the Laravel Core - all() is actually a static function
public static function all($columns = array('*'))
You have two options:
public static function getAllSortedByMake()
{
return Car::where('....')->get();
}
or
public function scopeGetAllSortedByMake($query)
{
return $query->where('...')->get();
}
Both will allow you to do
Car::getAllSortedByMake();
Actually you can extend Eloquent Builder and put custom methods there.
Steps to extend builder :
1.Create custom builder
<?php
namespace App;
class CustomBuilder extends \Illuminate\Database\Eloquent\Builder
{
public function test()
{
$this->where(['id' => 1]);
return $this;
}
}
2.Add this method to your base model :
public function newEloquentBuilder($query)
{
return new CustomBuilder($query);
}
3.Run query with methods inside your custom builder :
User::where('first_name', 'like', 'a')
->test()
->get();
for above code generated mysql query will be :
select * from `users` where `first_name` like ? and (`id` = ?) and `users`.`deleted_at` is null
PS:
First Laurence example is code more suitable for you repository not for model, but also you can't pipe more methods with this approach :
public static function getAllSortedByMake()
{
return Car::where('....')->get();
}
Second Laurence example is event worst.
public function scopeGetAllSortedByMake($query)
{
return $query->where('...')->get();
}
Many people suggest using scopes for extend laravel builder but that is actually bad solution because scopes are isolated by eloquent builder and you won't get the same query with same commands inside vs outside scope. I proposed PR for change whether scopes should be isolated but Taylor ignored me.
More explanation :
For example if you have scopes like this one :
public function scopeWhereTest($builder, $column, $operator = null, $value = null, $boolean = 'and')
{
$builder->where($column, $operator, $value, $boolean);
}
and two eloquent queries :
User::where(function($query){
$query->where('first_name', 'like', 'a');
$query->where('first_name', 'like', 'b');
})->get();
vs
User::where(function($query){
$query->where('first_name', 'like', 'a');
$query->whereTest('first_name', 'like', 'b');
})->get();
Generated queries would be :
select * from `users` where (`first_name` like ? and `first_name` like ?) and `users`.`deleted_at` is null
vs
select * from `users` where (`first_name` like ? and (`id` = ?)) and `users`.`deleted_at` is null
on first sight queries look the same but there are not. For this simple query maybe it does not matter but for complicated queries it does, so please don't use scopes for extending builder :)
for better dynamic code, rather than using Model class name "Car",
just use "static" or "self"
public static function getAllSortedByMake()
{
//to return "Illuminate\Database\Query\Builder" class object you can add another where as you want
return static::where('...');
//or return already as collection object
return static::where('...')->get();
}
Laravel model custom methods -> best way is using traits
Step #1: Create a trait
Step #2: Add the trait to model
Step #3: Use the method
User::first()->confirmEmailNow()
app/Model/User.php
use App\Traits\EmailConfirmation;
class User extends Authenticatable
{
use EmailConfirmation;
//...
}
app/Traits/EmailConfirmation.php
<?php
namespace App\Traits;
trait EmailConfirmation
{
/**
* Set email_verified_at to now and save.
*
*/
public function confirmEmailNow()
{
$this->email_verified_at = now();
$this->save();
return $this;
}
}
I'm trying to implement an "approved' state for a table I have, it's pretty straightforward, basically, if the row's approve column equals 1; that row should be retrieved, otherwise it shouldn't.
The problem is, now I have to go through the whole codebase and add a WHERE statement(i.e., function call) which is not only time consuming but also inefficient(if I ever want to remove that feature, etc.)
How can I do that? Is it as easy as adding $this->where(..) inside the Eloquent child class' constructor? Wouldn't that affect other CRUD operations? such as not updating an unapproved row?
The answer was given when there was no query scope feature available.
You can override the main query, only for the Post model, like
class Post extends Eloquent
{
protected static $_allowUnapprovedPosts = false;
public function newQuery()
{
$query = parent::newQuery();
if (!static::$_allowUnapprovedPosts) {
$query->where('approved', '=', 1);
} else {
static::$_allowUnapprovedPosts = false;
}
return $query;
}
// call this if you need unapproved posts as well
public static function allowUnapprovedPosts()
{
static::$_allowUnapprovedPosts = true;
return new static;
}
}
Now, simply use anything, but unapproved users won't appear in the result.
$approvedPosts = Post::where('title', 'like', '%Hello%');
Now, if you need to retrieve all posts even unapproved ones then you can use
$approvedPosts = Post::allowUnapprovedPosts()->where('title', 'like', '%Hello%');
Update (Using the query scope):
Since, Laravel now provides Global Query Scopes, leverage that instead of this hacky solution, notice the date of this answer, it's too old and so much things changed by now.
// Using a local query scope
class Post extends Eloquent
{
public function scopeApproved($query)
{
return $query->where('approved', 1);
}
}
You can use it like:
$approvedPosts = Post::approved()->get();
The closest thing I found is Eloquent query scope.
Even though it requires a minor change in my code(prefixing queries) it still gives me what I'm looking with great flexibility.
Here's an example:
Create a function within the Eloquent child class:
class Post extends Eloquent {
public function scopeApproved($query)
{
return $query->where('approved', '=', 1/*true*/);
}
}
Then simply use it like this:
$approvedPosts = Post::approved()-><whatever_queries_you_have_here>;
Works perfectly. No ugly repeated WHERE function calls. easy to modify. Much easier to read(approved() makes much more sense than where('approved', '=', 1) )
You can use global scope for your need, docs for that are here : https://laravel.com/docs/5.6/eloquent#query-scopes
Good example is SoftDeletingScope which is applied to all queries by default on models which use SoftDeletes trait.
I've an ORM model (PHP Active Record), say, for a blogging system. I've something that's a post model that stores the number of likes. The post could either be a picture or quote (say), and they are different tables (and hence models).
The schema is that a post holds data like number of shares, likes, description, etc. along with either a picture or a quote.
So when writing getters for the post model I'm having to write
public function getX() {
if ($this->isPicture()) {
return $this->picture->getX();
}
else if ($this->isQuote()) {
return $this->quote->getX()
}
else {
return self::DEFAULT_X
}
}
I'm currently having to write this structure for many getter. Is there something I can do to avoid that?
PS: Tagged as PHP because that's my code in.
EDIT
Changed comments to code.
This is a model (and a corresponding table in the DB) that has more data than just a picture and quote. Example, description that's part of the post and doesn't reside on either the picture or the quote.
There's tables for pictures and quotes.
Using PHP Active Record and each of the three classes extends the generic model class provided by PHP Active Record.
The picture model has it's own data. Same for quote.
To expand on the idea of the Strategy pattern mentioned in the comments:
class Post {
// get the correct 'strategy'
public function getModel() {
if ($this->isPicture()) {
return $this->picture;
}
if ($this->isQuote()) {
return $this->quote;
}
return null;
}
// using the strategy
public function getX() {
$model = $this->getModel();
if (null === $model) {
return self::DEFAULT_X;
}
return $model->getX();
}
}
Each strategy would presumably implement the same interface as the Post class for exposing those getters. Even better would be to provide a default strategy (rather than returning null) and have that return the default values. That way, the null check in each getter becomes redundant.
An alternative approach to this is a very basic form of metaprogramming. The idea is that you go a level higher than calling your methods by hand, and let the code do it for you.
(Assume that the method definitions are all part of Post)
public function getX($model = null) {
if ($model) return $model->getX();
else return self::DEFAULT_X;
}
// usage
$postModel->getX($pictureModel);
What's happening here is that, in this single instance of getX in your Post model, you're passing in the name of another class, and executing the `getX' method on that instance (if it exists and is callable).
You can extend this in other ways. For example, maybe you don't want to pass an instance in, when the method can do it anyway:
public function getX($model_name = null) {
if ($model_name && $class_exists($model_name) && is_callable(array($model_name, 'getX')) {
$model = new $model_name;
return $model->getX();
} else {
return self::DEFAULT_X;
}
}
// usage
$postModel->getX('Picture');
In this instance, you pass the model in as a string, and the method will do the rest. While this makes it quicker to get what you want, you might find that you don't want to work with fresh instances all the time (or you can't), so there's a bit of a trade-off with this 'convenient' way.
That still doesn't fully solve your problem, though, since you still have to repeat that for each getter, over and over again. Instead, you can try something like this:
public function __call($method, $args) {
$class = $args[0];
if (class_exists($class) && is_callable(array($class, $method))) {
$model = new $class;
return $model->$method();
}
}
// usage
$postModel->getX('Picture');
$postModel->getY('Quote');
$postModel->getZ('Picture');
If you call a function that doesn't exist on the Post model, that magic method will be called, and it'll fire up a new instance of the model name you supply as an argument, and call the getWhatever method on it, if it exists.
It's important to note that you must not define these getters in Post, unless you want to override the methods in the other classes.
There is still the problem of this creating new instances all the time, though, and to remedy this you can use a bit of dependency injection. This means that you let the Post class contains a list of other instances of classes that it wants to use in future, so you can add and remove them at will.
This is what I would consider the actual solution, with the other examples hopefully showing how I've got here (will edit to clarify things, of course).
public $models = array();
public function addModel($instance) {
$this->models[get_class($instance)] = $instance;
}
public function __call($method, $args) {
$class = $args[0];
if (array_key_exists($class, $this->models)) {
$model = $this->models[$class];
if (is_callable(array($model, $method)) {
return $model->$method();
}
}
}
// usage
$this->addModel($pictureModel);
$this->addModel($quoteModel);
$this->getX('Picture');
$this->getY('Quote');
Here, you're passing in your existing instances of models into the Post class, which then stores them in an array, keyed by the name of the class. Then, when you use the class as described in the last example, instead of creating a new instance, it will use the instance it has already stored. The benefit of this is that you might do things to your instances that you'd want reflected in the Post model.
This means that you can add as many new models as you like that need to plug into Post, and the only thing you need to do is inject them with addModel, and implement the getters on those models.
They all require you to tell the class what models to call at some point or another. Since you have an array of dependent models, why not add a way to get everything?
public function __call($method, $args) {
$class = $args[0];
if (array_key_exists($class, $this->models)) {
$model = $this->models[$class];
if (is_callable(array($model, $method)) {
return $model->$method();
}
} elseif ($class === 'all') {
// return an array containing the results of each method call on each model
return array_map(function($model) use ($method) {
if (is_callable(array($model, $method) return $model->$method();
}, $this->models);
}
}
// usage
$postModel->getX('all');
Using this, you'll get an array containing the return values of each getX method on each model you added with addModel. You can create pretty powerful functions and classes that do all this stuff without you having to repeat tedious logic.
I have to mention that these examples are untested, but at the very least I hope the concept of what you can do has been made clear.
Note:
The same thing can be applied to __GET and __SET methods, too, which are used for accessing properties. It's also worth saying that there may be the slight risk of a library already using these magic methods, in which case you'll need to make the code a little more intelligent.