I have a custom reporting feature I'm trying to build. To keep it short, the user is presented with options to choose from, those options are used to build an Eloquent query that returns data from the database to the front-end, in the form of an HTML table.
I'd like to switch my HTML table out for a Livewire Datatable. To do this, I need to build columns on the fly, based on what the user selects from the filters.
The datatable:
// $cols contains all the necesarry data from the user's selection to build columns the Livewire Datatable can understand.
public function displayReport($cols)
{
// This processes the data and pushes each new column into $this->cols
foreach($cols as $key => $val) {
$instance = app()->make($val['column']);
$columnName = $val['name'];
$item = $instance->name($columnName);
$item->filterable = true;
$item->hideable = true;
array_push($this->cols, $item);
}
public function columns()
{
return $this->cols;
}
I have dd($this->cols) and it is exactly what is needed to build the datatable. The problem is with the refresh of the component (from what I can tell). Upon submitting the users selection and running the whole thing, I get the following error:
Despite my best troubleshooting efforts, I have not been able to get the table to hydrate properly upon submission of my new columns. I even put a dd($this->cols) right before return $this->cols in the column function, and it returns exactly what is expected. But it won't build the table, and it throws that error seen above.
Help is appreciated. Thank you so much. I know this is a weird and complex one.
Related
im new in laravel programming and im trying to do something with laravel Eloquent.
So i have 3 tables, dev, dev_project(many to many) and project.
i need to see in which projects the dev is part of, using the dev_project table, and get the corresponding id from projects.
after that i need to go to the table project, and use the id that I got from the many-to-many table to get the corresponding name of the projects in which that same dev is part of.
First im fetching data from my DB into a variable
$devproject = DB::table('dev_project')->where('id_dev', '=', Auth::user()->id)->get();
And then Im trying to get the data from the project table where the id is the same from the one that i fetched before
return Project::where('id', $dev_project->id_project);
The problem here is that the variable dev_project is an object, and i cant use a foreach and store the data that i want to return inside an array.
So how can I get all the data that i want in the same object.
I appreciate any help, Thank you.
Here, $devproject is a Collection, not a single row. $dev_project->id_project is not a valid property.
If you want the first result's id_project, then the syntax is $devproject->first()->id_project.
If you want a Collection of each of $devproject 's id_project, then the syntax should be $devproject->pluck('id_project').
With that in mind, your query should either be
return Project::where('id', $dev_project->first()->id_project);
or
return Project::whereIn('id', $dev_project->pluck('id_project'));
As harish durga suggests, a relationship would save you the trouble of writin these queries.
# Dev Model
public function projects()
{
return $this->belongsToMany(Project::class, 'dev_project', 'id_project', 'id_dev');
}
# Project Model
public function devs()
{
return $this->belongsToMany(Dev::class, 'dev_project', 'id_dev', 'id_project');
}
# Get all devs with associated projects:
$devs = Dev::with('projects')->get();
foreach ($devs as $dev) {
echo $dev->...
foreach ($dev->projects as $project) {
echo $project->...
}
}
I want to store the array data in a database, but in my database I'm getting only the first input tag of the form:
here is my controller:
public function store(Request $request)
{
$qt = Qt::all();
$Itemname = $request->input('Itemname');
$Quantity = $request->input('Quantity');
$Price = $request->input('Price');
$Tax = $request->input('Tax');
$Total = $request->input('Total');
$GrandTotal = $request->input('GrandTotal');
$data = array('Itemname'=>$Itemname,'Quantity'=>$Quantity,'Price'=>$Price,'Tax'=>$Tax,'Total'=>$Total);
dd($data);
Qt::table('qts')->insert($data);
return redirect()->route('quotes.index');
}
I'm not sure what you mean by 'one data' - if you are only storing one field, that shouldn't be happening. The way you have this set up, it is data for one row. Thus, the expected output to the database would be a single Qt model saved. This would be what I would expect in a store() method based on a user entering a single item (with name, price, tax, etc). One form for one new Qt. And I think you have it almost set up for this.
However, Laravel makes the storage of a new model much easier. You don't need pretty much any of that code, because it looks like you have your incoming form fields set to correctly match the name of the fields on the Qt model in the database. Though, you might want to make them lowercase on both to match convention.
If the form fields are a match to the database fields on the model, you can remove almost everything in that method and replace it with:
public function store(Request $request)
{
Qt::create($request->all());
return redirect()->route('quotes.index');
}
The create method will take the $request object as is, and automatically set the right elements to the right fields if they are set to fillable on the model.
EDIT:
I understand you are looking to make multiple rows of items for a single customer based on many items coming in through your form. However, I don't know where that information is coming from -- I don't know where the array of items is within your form (or $request object). This might be your reason why you are having an error in the first place: I don't see how to loop or find multiple items, I just see one item coming in, which would produce one row into the database based on that form. The above code is correct based on what you have said is coming from the form.
However, based on the parameterize() error you mention in the comments, you likely have an array somewhere that is causing the parameter issue, and which would help you to create multiple rows. You don't show what your array is, but you mention that these many items would be attached to a single customer.
Here is a possible way to do this. I will make up a few variables based on what you've said (some array to loop on, that there is a 'customer' object or similar, etc.)
public function store(Request $request)
{
$customer = Customer::find($request->get('customer_id'));
// I don't know how you bring in the customer's id - maybe as a $request item or perhaps through GET?
$newItemsArray = []; // Store the rows of new items here
// Whatever the array of items is, pull it and loop on it to create rows of items:
foreach($request->get('someItemsArray') as $newItem){
// Assuming the name of the array fields match the database field names:
// Assumes $newItem is an associative array
$newItemsArray['Itemname'] = $newItem['Itemname'];
$newItemsArray['Quantity'] = $newItem['Quantity'];
$newItemsArray['Price'] = $newItem['Price'];
$newItemsArray['Tax'] = $newItem['Tax'];
$newItemsArray['Total'] = $newItem['Total'];
}
// Here is where you can create all the rows and attach them at the same time to your customer
$customer->Qts()->createMany($newItemsArray);
return redirect()->route('quotes.index');
}
This will allow for creating multiple rows of items and attach to a customer all at once.
You'll need to fill in the blanks - how are you sending the multiple rows from your form, how are you sending the customer who is adding the items, how to deal with GrandTotal (is it an array item, or is it a single field passed from the form, based on a calculation), etc. But, this will answer your question on the controller side to get multiple rows in for a customer.
We have a COMMON database and then tenant databases for each organization that uses our application. We have base values in the COMMON database for some tables e.g.
COMMON.widgets. Then in the tenant databases, IF a table called modified_widgets exists and has values, they are merged with the COMMON.widgets table.
Right now we are doing this in controllers along the lines of:
public function index(Request $request)
{
$widgets = Widget::where('active', '1')->orderBy('name')->get();
if(Schema::connection('tenant')->hasTable('modified_widgets')) {
$modified = ModifiedWidget::where('active', '1')->get();
$merged = $widgets->merge($modified);
$merged = array_values(array_sort($merged, function ($value) {
return $value['name'];
}));
return $merged;
}
return $countries;
}
As you can see, we have model for each table and this works OK. We get the expected results for GET requests like this from controllers, but we'd like to merge at the Laravel MODEL level if possible. That way id's are linked to the correct tables and such when populating forms with these values. The merge means the same id can exist in BOTH tables. We ALWAYS want to act on the merged data if any exists. So it seems like model level is the place for this, but we'll try any suggestions that help meet the need. Hope that all makes sense.
Can anyone help with this or does anyone have any ideas to try? We've played with overriding model constructors and such, but haven't quite been able to figure this out yet. Any thoughts are appreciated and TIA!
If you put this functionality in Widget model you will get 2x times of queries. You need to think about Widget as an instance, what I am trying to say is that current approach does 2 queries minimum and +1 if tenant has modified_widgets table. Now imagine you do this inside a model, each Widget instance will pull in, in a best case scenario its equivalent from different database, so for bunch of Widgets you will do 1 (->all())+n (n = number of ModifiedWidgets) queries - because each Widget instance will pull its own mirror if it exists, no eager load is possible.
You can improve your code with following:
$widgets = Widget::where('active', '1')->orderBy('name')->get();
if(Schema::connection('tenant')->hasTable('modified_widgets')) {
$modified = ModifiedWidget::where('active', '1')->whereIn('id', $widgets->pluck('id'))->get(); // remove whereIn if thats not the case
return $widgets->merge($modified)->unique()->sortBy('name');
}
return $widgets;
OK, here is what we came up with.
We now use a single model and the table names MUST be the same in both databases (setTable does not seem to work even though in exists in the Database/Eloquent/Model base source code - that may be why it's not documented). Anyway = just use a regular model and make sure the tables are identical (or at least the fields you are using are):
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Widget extends Model
{
}
Then we have a generic 'merge controller' where the model and optional sort are passed in the request (we hard coded the 'where' and key here, but they could be made dynamic too). NOTE THIS WILL NOT WORK WITH STATIC METHODS THAT CREATE NEW INSTANCES such as $model::all() so you need to use $model->get() in that case:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
class MergeController extends Controller
{
public function index(Request $request)
{
//TODO: add some validations to ensure model is provided
$model = app("App\\Models\\{$request['model']}");
$sort = $request['sort'] ? $request['sort'] : 'id';
$src_collection = $model->where('active', '1')->orderBy('name')->get();
// we setup the tenants connection elsewhere, but use it here
if(Schema::connection('tenant')->hasTable($model->getTable())) {
$model->setConnection('tenant');
$tenant_collection = $model->get()->where('active', '1');
$src_collection = $src_collection->keyBy('id')->merge($tenant_collection->keyBy('id'))->sortBy('name');
}
return $src_collection;
}
}
If you dd($src_collection); before returning it it, you will see the connection is correct for each row (depending on data in the tables). If you update a row:
$test = $src_collection->find(2); // this is a row from the tenant db in our data
$test->name = 'Test';
$test->save();
$test2 = $src_collection->find(1); // this is a row from the tenant db in our data
$test2->name = 'Test2'; // this is a row from the COMMON db in our data
$test2->save();
dd($src_collection);
You will see the correct data is updated no matter which table the row(s) came from.
This results in each tenant being able to optionally override and/or add to base table data without effecting the base table data itself or other tenants while minimizing data duplication thus easing maintenance (obviously the table data and population is managed elsewhere just like any other table). If the tenant has no overrides then the base table data is returned. The merge and custom collection stuff have minimal documentation, so this took some time to figure out. Hope this helps someone else some day!
I have a nasty problem. I want to get rid of a certain database field, but I'm not sure in which bits of code it's called. Is there a way to find out where this field is used/called from (except for text searching the code; this is fairly useless seeing as how the field is named 'email')?
Cheers
I would first text search the files for the table name, then only search the tables that contain the table name for the field name.
I wrote a program to do this for my own purposes. It builds an in-memory listing of tables and fields and relates the tables to the fields. Then it loops through tables, searching for the code files that contain the table names, and then searches those files for the fields in the tables found. I'd recommend a similar methodology in your case.
setting mysql to log all queries for some time might help. the queries will give you the tip where to look
brute force - set up a test instance - remove the column - and excercise your test suite.
create a before insert trigger on that table that monitors the insertion on that column.
at the same time create another table called monitor with only one column email
make that table insert the value of NEW.email field into monitor.email as well as in real table.
so you can run your application and check for the existence of any non-null value in monitor table
You should do this in PHP i would expect
For example:
<?php
class Query
{
var $command;
var $resource;
function __construct($sql_command = '')
{
$this->command = $sql_command;
}
public function setResource($resource)
{
$this->resource = $resource;
}
}
//then you would have some kind of database class, but here we would modify the query method.
class Database
{
function query(Query $query)
{
$resource = mysql_query($query->command);
$query->setResource($resource);
//Then you can send the class to the monitor
QueryMonitor::Monitor($query);
}
}
abstract class QueryMonitor
{
public static Monitor(Query $query)
{
//here you use $query->resource to do monitoring of queryies
//You can also parse the query and gather what query type it was:-
//Select or Delete, you can also mark what tables were in the Query
//Even meta data so
$total_found = mysql_num_rows($query->resource);
$field_table = mysql_field_table ($query->resource);
//Just an example..
}
}
?>
Obviously it would be more advanced than that but you can set up a system to monitor every query and every queries meta data in a log file or w.e
I'm using the cacheCounter in CakePHP, which increments a counter for related fields.
Example, I have a Person table a Source table. Person.source_id maps to a row in the Source table. Each person has one Source, and each Source has none or many Person rows.
cacheCounter is working great when I change the value of a source on a person. It increments Source.Person_Count. Cool.
But when it increments, it adds it to the destination source for a person, but doesn't remove it from the old value. I tried updateCacheControl() in afterSave, but that didn't do anything.
So then I wrote some code in my model for afterSave that would subtract the source source_id, but it always did this even when I wasn't even changing the source_id. (So the count went negative).
My question: Is there a way to tell if a field was changed in the model in CakePHP?
To monitor changes in a field, you can use this logic in your model with no changes elsewhere required:
function beforeSave() {
$this->recursive = -1;
$this->old = $this->find(array($this->primaryKey => $this->id));
if ($this->old){
$changed_fields = array();
foreach ($this->data[$this->alias] as $key =>$value) {
if ($this->old[$this->alias][$key] != $value) {
$changed_fields[] = $key;
}
}
}
// $changed_fields is an array of fields that changed
return true;
}
With reference to Alexander Morland Answer.
How about this instead of looping through it in before filter.
$result = array_diff_assoc($this->old[$this->alias],$this->data[$this->alias]);
You will get key as well as value also.
You could use ->isDirty() in the entity to see if a field has been modified.
// Prior to 3.5 use dirty()
$article->isDirty('title');
check the doc: https://book.cakephp.org/3/en/orm/entities.html#checking-if-an-entity-has-been-modified
Edits happen infrequently, so another select before you do the update is no big deal, so, fetch the record before you save, save it, compare the data submitted in the edit form with the data you fetched from the db before you saved it, if its different, do something.
In the edit view, include another hidden field for the field you want to monitor but suffix the field name with something like "_prev" and set the value to the current value of the field you want to monitor. Then in your controller's edit action, do something if the two fields are not equal. e.g.
echo $form->input('field_to_monitor');
echo $form->hidden('field_to_monitor_prev', array('value'=>$form->value('field_to_monitor')));
See if the "save" uses some sort of DBAL call that returns "affected rows", usually this is how you can judge if the last query changed data, or if it didn't. Because if it didn't, the affected rows after an UPDATE-statement are 0.
You can call getAffectedRows() on any model class.
From class Model :
/**
* Returns the number of rows affected by the last query
*
* #return int Number of rows
* #access public
*/
function getAffectedRows() {
$db =& ConnectionManager::getDataSource($this->useDbConfig);
return $db->lastAffected();
}