When I'm using eloquent, I can use the "where" method then the method 'get' to fill an object containing what I've selected in my database.
I mean:
$users = User::where('gender', 'M')->where('is_active', 1)->get(['pseudo', 'email', 'age', 'created_at'])->toArray();
Here I can choose the columns I want to get like 'pseudo', 'email', etc..
But what I miss in laravel doc is the way to do the contrary.
It could be something like that:
$users = User::where('gender', 'M')->where('is_active', 1)->notGet(['pseudo', 'email', 'age', 'created_at'])->toArray();
Thank you for you futur answer and have a nice day.
If you only need to hide attributes from your model's array or JSON representation, you may use one or both approaches:
Add the
$hidden property to your model
class User extends Model
{
/**
* The attributes that should be hidden for arrays.
*/
protected $hidden = ['password'];
}
Use the
makeHidden
function
$users = $users->makeHidden(['address', 'phone_number']);
See other answers for more details... But sometimes you don't want to load huge data (geospatial, html, logs...) into your application, it will be slow and take more memory. OP asked for an SQL query hence my answer, but most of the time it's more convenient to only hide the data from the JSON response.
AFAIK there is no build in option in SQL to exclude columns explicitly, so Laravel can't do it. But you can try this trick
Update
Another trick is to specify all columns in your model (or use an extra query to get all columns using $this->getTableColumns() from this answer, it can also be cached after each migration to avoid two queries) then add a local scope function
// The below code requires you to define all columns in $columns.
// A better approach is to query the schema of the table and cache it after each
// migration, for more details: https://stackoverflow.com/a/56425794/3192276
protected $columns = ['id','pseudo','email'];
public function scopeExclude($query, $value = [])
{
return $query->select(array_diff($this->columns, (array) $value));
}
Then you can do :
$users = User::where('gender', 'M')
->where('is_active', 1)
->exclude(['pseudo', 'email', 'age', 'created_at'])
->toArray();
using hidden array in model is good, but if you don't want to hide your column all the time and use makeVisible to access them in need, then instead, hide your column from serialization where you need with makeHidden function like this :
$res = Model::where('your query')->get();
$res->makeHidden(['column_one','column_two','column_n']);
return response()->json($res);
I don't know about previous Laravel version, but in 5.4 you can put this line in User model
protected $hidden = ['pseudo', 'email', 'age', 'created_at'];
and then User::find(1); will return all fields except pseudo, email, age, and created_at.
But you still can retrieve those hidden fields by using:
$user = User::find(1);
$email = $user['email']; // or $user->email;
I have looked into the answer by #Razor
But there is Very Conveinent way by skipping $columns property
/**
* Scope a query to only exclude specific Columns.
*
* #author Manojkiran.A <manojkiran10031998#gmail.com>
* #param \Illuminate\Database\Eloquent\Builder $query
* #return \Illuminate\Database\Eloquent\Builder
*/
public function scopeExclude($query, ...$columns)
{
if ($columns !== []) {
if (count($columns) !== count($columns, COUNT_RECURSIVE)) {
$columns = iterator_to_array(new \RecursiveIteratorIterator(new \RecursiveArrayIterator($columns)));
}
return $query->select(array_diff($this->getTableColumns(), $columns));
}
return $query;
}
/**
* Shows All the columns of the Corresponding Table of Model
*
* #author Manojkiran.A <manojkiran10031998#gmail.com>
* If You need to get all the Columns of the Model Table.
* Useful while including the columns in search
* #return array
**/
public function getTableColumns()
{
return \Illuminate\Support\Facades\Cache::rememberForever('MigrMod:'.filemtime(database_path('migrations')).':'.$this->getTable(), function () {
return $this->getConnection()->getSchemaBuilder()->getColumnListing($this->getTable());
});
}
getTableColumns function will get all the columns of the table so you dont need to define the $column property
NOTE: COLUMN NAMES OF TABLE WILL BE CACHED UNTIL CONTENTS OF MIGRATIONS DIRECTORY IS ADDED OR DELETED.
MODIFYING THE CONTENTS OF FILES INSIDE THE MIGRATIONS DIRECTORY WILL
NOT RE-CACHE THE COLUMNS
To clear cache manually you can run php artisan cache:clear
you can use hidden array like this:
class Promotion extends Model
{
protected $table = 'promotion';
protected $hidden = array('id');
}
I have a solution that worked for me, which is slightly different than those already stated.
Solution:
$all_columns = Schema::getColumnListing('TABLE_NAME');
$exclude_columns = ['COLUMN_TO_EXCLUDE_1', 'COLUMN_TO_EXCLUDE_2'];
$get_columns = array_diff($all_columns, $exclude_columns);
return User::select($get_columns)->get();
Reasoning:
For me:
Razor's answer didn't work as I got the following error:
BadMethodCallException with message 'Call to undefined method App/CaseStudy::exclude()'
Then, the remaining answers were attemping to hide the columns within the model. Unfortunately, that would hide them for each method in my class and this isn't something that I wanted.
So, in the end, I modified Razor's solution so that it would work without having to hide any of the columns for each method.
I hope this helps someone! 😊
We get the object eloquent from the model full with all fields, transform it to array and we put it inside of a collection. Than we get all fields except all fields specified in array $fields.
$fields = ['a', 'b', 'c', 'N'];
$object = Model::find($id);
return collect($object->toArray())->except($fields);
More clearly, let's give an example:
// Array of fields you want to remove
$fields_to_remove = ['age', 'birthday', 'address'];
// Get the result of database
$user = User::find($id);
// Transform user object to array
$user = $user->toArray();
// Create a collection with the user inside
$collection = collect($user);
// Get all fields of our collection except these fields we don't want
$result = $collection->except($fields_to_remove);
// Return
return $result;
This example above makes exactly the same thing of the first one, but it's more explained.
you can use makeHidden array like this: (After get() or all())
$users = User::where('gender', 'M')->where('is_active', 1)->get()->makeHidden(['pseudo', 'email', 'age', 'created_at'])->toArray();
You can leverage Illuminate\Support\Facades\Schema::getColumnListing('table_name');
use Illuminate\Support\Facades\Schema;
$users_table_columns = Schema::getColumnListing('users');
$exclude_columns = [
'password',
'token',
'address',
];
$select = array_diff($users_table_columns, (array) $exclude_columns);
$site = User::select($select)
->where('gender', 'M')
->where('is_active', 1)
->first();
I wrapped a slitly changed approach from #manojkiran-a up in a small package, cause I needed it in multiple projects:
https://github.com/laracraft-tech/laravel-useful-traits/#selectallbut
Install via composer:
composer require laracraft-tech/laravel-useful-traits
This is how it is working:
use LaracraftTech\LaravelUsefulTraits\UsefulScopes;
$class = new class extends Model
{
use UsefulScopes;
protected $timestamps = false;
protected $table = 'scope_tests';
};
$class->create([
'foo' => 'foo',
'bar' => 'bar',
'quz' => 'quz',
]);
$class::query()->selectAllBut(['foo'])->first()->toArray();
// return ['bar' => 'bar', 'quz' => 'quz']
Note: Since you can't do a native "select all but x,y,z" in mysql, we need to query (and cache) the existing columns of the table, and then exclude the given columns which should be ignored (not selected) from the existing columns.
Cache: Column names of each table will be cached until contents of migrations directory is added or deleted. Modifying the contents of files inside the migrations directory will not re-cache the columns. Consider to clear the cache whenever you make a new deployment/migration!
You can use unset unset($category->created_at,$category->updated_at);
$fcategory = array();
$kCategory = KCategory::where("enabled", true)->get();
foreach ($kCategory as $category) {
$subkCategory = PostCategory::select("id", "name", "desc")
->where("id_kcategory", $category->id)
->where("enabled", true)
->get();
unset($category->created_at, $category->updated_at);
$fcategory[] = $category;
}
I want to loop over a collection of items and attach a relationship based on if a particular condition is satisfied. Here is my code
public function bulkAssign()
{
$trainers = MasterTrainer::all();
for ($i=0; $i < count($trainers); $i++) {
$this->assignToManager($trainers[$i]);
}
// return redirect()->back()->with('success', 'Project Managers Assigned Successfully');
}
private function assignToManager($trainer)
{
$manager = ProjectManager::where('state', $trainer->state)->first();
return $trainer->update([
'project_manager_id' => $manager->id
]);
}
What I get is it attaches only the first manager to all the elements in the collection. What am i doing wrong?
can you inline the func for now? do some sort of echo/debugging?
but also I see several issues:
yes do use foreach because that is a bit better and you avoid having to use $i (making code a little more easy to read)
you are not attaching a relationship, you are setting a project_manager_id (i say this because initially i automatically thought you were going to dynamically add a relationship to model)
without knowing your db schema.. could you not do some sort of trick to avoid having to do this nth times?
$manager = ProjectManager::where('state', $trainer->state)->first();
you could either do:
$states = $trainers->pluck('states');
$managers = // do a query to get one trainer per state using group by
foreach ($trainers... ) {
$manager = $managers->where('state', $trainers->state)->first() // this is collection not eloquent
$trainer->update([
'project_manager_id' => $manager->id
]);
other would be to create a scope where you do a sub query to get manager id when u query for trainers
I am working with a system where inventory is stored in a single database table. I need help to find the best way to add/remove items from this.
It is stored as follows;
3:1|8:2|5:3|4:4
the first number represents quantity and the second number is the item ID. The Pipe | splits these items.
So 3:1 = quantity 3 of item 1.
I am trying to use PHP to look at this string, find if the item is there. If it is add to or remove from it. OR if the item isn't there then create it.
I know I would have to use an array to achieve this but I'm a little lost at how this would be done, any help with this would be greatly appreciated.
Thanks.
I would make a small class that holds an item -> quantity map (stored as an array) that is created from that string, and builds the string back when requested.
This makes use of:
explode to split the string parts,
implode combined with array_map to join them back.
Code:
class DataStructure
{
private $data = [];
public function __construct(string $data)
{
foreach (explode('|', $data) as $item_quantity) {
list($quantity, $item) = explode(':', $item_quantity);
$this->data[$item] = (int)$quantity;
}
}
public function getItemQuantity(int $item): ?int
{
return $this->data[$item] ?? null;
}
public function setItemQuantity(int $item, int $quantity)
{
$this->data[$item] = $quantity;
}
public function __toString(): string
{
return implode('|', array_map(function ($item) {
return $this->data[$item] . ':' . $item;
}, array_keys($this->data)));
}
}
Note that this doesn't include error handling, for the purpose of this example.
Demo: https://3v4l.org/pqRQh
Demo (PHP 5.6 compatible): https://3v4l.org/tK9Q9
I want to test whether my sorting works properly with Laravel TestCase. I have simple test page:
<div id="values">
#foreach(['Value1', 'Value2'] as $value)
<div class="test-class">{{$value}}</div>
#endforeach
</div>
And now i want to test if the first .test-class element contains 'Value1'. I had tried different approaches, and had no success. Here are first one:
public function testSorting()
{
$this->visit(route('dummy'));
$this->see('Value1');
$this->seeInElement('.test-class:first-of-type', 'Value2');
}
This one resulted in exception:
Symfony\Component\CssSelector\Exception\ExpressionErrorException: "*:first-of-type" is not implemented.
Then I tried
$this->seeInElement('#values:first-child', 'Value2');
and my test when green. But it should be red, because first child is 'Value1'. It totally ignored ':first-child' part.
Then I tried
$this->seeInElement('#values:nth-child(1)', 'Value2');
and got green too. So this approach doesn't work either.
Have I missed something? Or there is no way to test order of elements on page with Laravel tests?
I'm late to the party here, but I've been using the below to check the ordering of items on the page. Seems to work out so well so far.
Implement the below method on your TestCase base class.
/**
* Given a CSS selector string, check if the elements matching the query
* contain the values provided, in the order they are provided.
*
* #param string $selector
* #param array $contents
* #return $this
*/
public function seeInOrder($selector, Array $contents)
{
$matches = $this->crawler->filter($selector);
try {
foreach ($matches as $index => $domElement) {
$needle = $contents[$index];
$this->assertContains($needle, trim($domElement->textContent));
}
} catch (PHPUnit_Framework_ExpectationFailedException $e) {
$this->fail('Failed asserting that the element at index ' . $index . ' contains the string "' . $contents[$index] . '"');
}
return $this;
}
Usage:
/**
* Test that the /books route returns a list of books in explicit order.
*
* #test
* #return void
*/
public function books_index_page_lists_books_in_explicit_order()
{
$book_1 = factory(App\Book::class)->create([
'order' => 0
]);
$book_2 = factory(App\Book::class)->create([
'order' => 1
]);
$this->visit('/books')
->see($book_1->title)
->see($book_2->title)
->seeInOrder('.books .book', [
$book_1->title,
$book_2->title
]);
}
Not a solution for your specific question, but a workaround for your problem:
You could enumerate your test-class divs with the $loop->iteration helper variable, which contains the number of the current loop iteration.
<div id="values">
#foreach(['Value1', 'Value2'] as $value)
<div class="test-class" id="value_{{$loop->iteration}}">{{$value}}</div>
#endforeach
</div>
Now your div classes have an enumerated id beginning with 1 and you can use the selector #value_1 in your test to select the first div:
$this->seeInElement('#value_1', 'Value2');
I stumbled onto this question for my HTTP test, if you're using that no need to build your own function like in the answers below. Laravel ships with a bunch of assertions, including assertSeeInOrder:
https://laravel.com/docs/7.x/http-tests#assert-see-in-order
So, i have solved it. The key was to look into \Symfony\Component\DomCrawler\Crawler class. It has a lot of useful methods like first(), last() or eq($number). I have created this method in my TestCase class:
public function seeInFirstElement($selector, $text)
{
$this->assertContains($text, trim($this->crawler->filter($selector)->first()->text()));
return $this;
}
You can get rid of trim() function but with it the output looks better in case of failed test.
So, in my tests I use such constructions to test ordering:
$this->seeInFirstElement('.test-class', 'Value1');
In my migration file, I gave my table pages a enum field with 2 possible values (as seen below). My question is, if it's possible to select these values with Laravels Eloquent?
$table->enum('status', array('draft','published'));
There are several Workarounds that I found, but there must be some "eloquent-native" way to handle this. My expected output would be this (that would be perfect!):
array('draft','published')
Thank you in advance!
Unfortunately, Laravel does not offer a solution for this. You will have to do it by yourself. I did some digging and found this answer
You can use that function and turn it into a method in your model class...
class Page extends Eloquent {
public static function getPossibleStatuses(){
$type = DB::select(DB::raw('SHOW COLUMNS FROM pages WHERE Field = "type"'))[0]->Type;
preg_match('/^enum\((.*)\)$/', $type, $matches);
$values = array();
foreach(explode(',', $matches[1]) as $value){
$values[] = trim($value, "'");
}
return $values;
}
}
And you use it like this
$options = Page::getPossibleStatuses();
If you want you can also make it a bit more universally accessible and generic.
First, create a BaseModel. All models should then extend from this class
class BaseModel extends Eloquent {}
After that, put this function in there
public static function getPossibleEnumValues($name){
$instance = new static; // create an instance of the model to be able to get the table name
$type = DB::select( DB::raw('SHOW COLUMNS FROM '.$instance->getTable().' WHERE Field = "'.$name.'"') )[0]->Type;
preg_match('/^enum\((.*)\)$/', $type, $matches);
$enum = array();
foreach(explode(',', $matches[1]) as $value){
$v = trim( $value, "'" );
$enum[] = $v;
}
return $enum;
}
You call this one like that
$options = Page::getPossibleEnumValues('status');
Made a small improvement to lukasgeiter's function. The foreach loop in his answer is parsing the string. You can update the regex to do that for you.
/**
* Retrieves the acceptable enum fields for a column
*
* #param string $column Column name
*
* #return array
*/
public static function getPossibleEnumValues ($column) {
// Create an instance of the model to be able to get the table name
$instance = new static;
// Pulls column string from DB
$enumStr = DB::select(DB::raw('SHOW COLUMNS FROM '.$instance->getTable().' WHERE Field = "'.$column.'"'))[0]->Type;
// Parse string
preg_match_all("/'([^']+)'/", $enumStr, $matches);
// Return matches
return isset($matches[1]) ? $matches[1] : [];
}
This throws an error if the column does not exist. So I added a small check in the code
public static function getPossibleEnumValues ($column) {
// Create an instance of the model to be able to get the table name
$instance = new static;
$arr = DB::select(DB::raw('SHOW COLUMNS FROM '.$instance->getTable().' WHERE Field = "'.$column.'"'));
if (count($arr) == 0){
return array();
}
// Pulls column string from DB
$enumStr = $arr[0]->Type;
// Parse string
preg_match_all("/'([^']+)'/", $enumStr, $matches);
// Return matches
return isset($matches[1]) ? $matches[1] : [];
}
As of L5.17 Eloquent does not include this functionality, instead you need to fall back to native QL. Here's an example that will work with SQL and in one line - returning an array like you asked.
In the spirit of one liner complexity ;)
I threw this in one of my view composers - it fetches the column from the table, explodes it and assembles the values in an array.
I iterate over that in my views using a foreach.
explode (
"','",
substr (
DB::select(" SHOW COLUMNS
FROM ".(new \Namespace\Model)->getTable()."
LIKE 'colName'"
)[0]->Type,
6,
-2
)
);