I've written a class which takes an Eloquent Model and manipulates it in order to output JSON for a datatable. The datatable uses server side processing via a POST request.
Here is the class:
namespace App\Helpers;
class Datatable {
public static $request;
public static $model;
public static $records_total;
public static $records_filtered;
static function make ($request, $model) {
self::$request = $request;
self::$model = $model;
self::setRecordsTotal();
self::filter();
self::setRecordsFiltered();
self::orderLimit();
self::renderJson();
}
// set total record count
static function setRecordsTotal () {
self::$records_total = self::$model->count();
}
// filter by search query
static function filter () {
if (!empty(self::$request['search']['value'])) {
foreach (self::$request['columns'] as $column) {
if ($column['searchable'] == 'true') {
self::$model->where($column['data'], 'LIKE', '%'.self::$request['search']['value'].'%');
}
}
}
}
// set filtered record count
static function setRecordsFiltered () {
self::$records_filtered = self::$model->count();
}
// apply order by & limit
static function orderLimit () {
self::$model->orderBy(self::$request['columns'][self::$request['order'][0]['column']], self::$request['order'][0]['dir']);
self::$model->skip(self::$request['start'])->take(self::$request['length']);
}
// render json output
static function renderJson () {
$array = [];
$array['draw'] = self::$request['draw'];
$array['recordsTotal'] = self::$records_total;
$array['recordsFiltered'] = self::$records_filtered;
$array['data'] = [];
$results = self::$model->get();
foreach ($results as $result) {
$array['data'][] = $result->toArray();
}
echo json_encode($array);
}
}
Here is how I call the class:
Datatable::make($_POST, new User());
So User is an Eloquent model in this instance.
Now, the initial rendering of the datatable works great. However, when I try and search or order it, it seems like my code within the filter() and orderLimit() methods is not being applied, because it just keeps spitting out the exact same results in the exact same order.
Why are my where(), orderBy(), etc. not being applied to the Model properly?
Figured it out. I just had to use ::query() on the model in my make() method e.g:
self::$model = $model::query().
Related
In my model I have functions for create and find records.
Model
use Illuminate\Database\Eloquent\Model;
class Charge extends Model
{
use ChargeMutator;
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
$this->request = $attributes;
}
public function add() {
$result = self::create($this->request);
return $result;
}
public function find($id) {
$record = self::find($id);
return $record;
}
}
And I have a mutator to mutate the values.
<?php
trait ChargeMutator
{
public function getCostAttribute($value)
{
return $this->attributes['cost'] = // call function A;
}
public function getTotalCostAttribute($value)
{
dd(gettype($value));
return $this->attributes['total_cost'] = // call function A;
}
public function setCostAttribute($value)
{
return $this->attributes['cost'] = // call function B;
}
public function setTotalCostAttribute($value)
{
return $this->attributes['total_cost'] = // call function B;
}
}
Controller
class ChargesController extends Controller
{
use ValidateCreditNoteCharges, Messages, ApiResponser;
public function addCharge(Request $request)
{
// $this->validateAddCharge($request);
$credit_note_charge = new Charge($request->all());
$get_result = $credit_note_charge->add();
return $this->createdResponse($get_result);
}
}
the $request is like this
{
"name":"TEST",
"quantity":1,
"cost":56.00,
"total_cost":56.00
}
The Problem is:
when I dd(gettype($value)); in getTotalCostAttribute
from the add() function the type of total_cost value is string
from the find() function the type of total_cost value is integer
in the database the data type for total_cost is int(10)
I am not sure why is the type of the return values casted into strings when use create()
I have searched for this for 3 hours now and couldn't find a solution. i need the data type correctly because accoriding to the data type i do different calculations in my calling functions A and B
I have a controller in laravel, AppExportController. In one of my functions on that controller, I iterate over many records and return a file download. I decided I wanted to create a little function so I could cache a certain thing, a Zone Name in this instance.
This was my first attempt at writing a function to cache the zone names (the getZoneName function obviously):
<?php
namespace App\Http\Controllers;
class AppExportController extends Controller {
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct() {
$this->middleware('auth');
$this->middleware('client.approved');
}
public function prices(Request $request) {
$user = Auth::user();
...
$zoneNameCache = [];
function getZoneName($zoneId) use (&$zoneNameCache) {
try {
if (!empty($zoneNameCache[$zoneId])) {
return $zoneNameCache[$zoneId];
} else {
$zone = ServiceZone::find($zoneId);
$zoneNameCache[$zoneId] = $zone->name;
return $zone->name;
}
} catch(Exception $e) {
return '';
}
};
$prices = []; // I actually do a database query here, don't worry about that
$records = [];
foreach($prices as $price) {
// output to $records here
$records[] = [
...
getZoneName($price->service_zone_id),
...
];
}
return response();
}
}
This was making that route 500 error, and I tracked it down to being for sure the closure aspect of the function -- when I took out the use (&$zoneNameCache) part, it worked (but didn't cache anything of course).
So I tried another thing -- assigning the function to a variable instead. And that worked! With the closure, and caching was working!
<?php
namespace App\Http\Controllers;
class AppExportController extends Controller {
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct() {
$this->middleware('auth');
$this->middleware('client.approved');
}
public function prices(Request $request) {
$user = Auth::user();
...
$zoneNameCache = [];
$getZoneName = function ($zoneId) use (&$zoneNameCache) {
try {
if (!empty($zoneNameCache[$zoneId])) {
return $zoneNameCache[$zoneId];
} else {
$zone = ServiceZone::find($zoneId);
$zoneNameCache[$zoneId] = $zone->name;
return $zone->name;
}
} catch(Exception $e) {
return '';
}
};
$prices = []; // I actually do a database query here, don't worry about that
$records = [];
foreach($prices as $price) {
// output to $records here
$records[] = [
...
$getZoneName($price->service_zone_id),
...
];
}
return response();
}
}
I don't know why the second one should work but not the first one. Can anyone shed light on this?
Without assigning it to a variable, or returning it, it is not a closure.
This way you have function declaration, within another function or method in this case.
Which is not allowed, and therefore will give you a 500 for sure.
If you check your php error_log and probably your laravel log. It will tell you that.
If your do not want to assign it to a variable at that point, you could return it immediately
return function().......
I have a PHP class whose instances are rows in my database table. The class has methods that allow one to create, read, update, and delete rows corresponding to the given instance of the class. It works well as it stands, but I currently name each of the database columns explicitly in my code. As a result, when I add or delete columns (an occasional, but not trivially infrequent action), I must update the class to include those column names. I would like to abstract my code so that it queries the database first to determine what the columns it needs to fill in. (Incidentally, I'm working with PHP 7.2)
The code below is what I've got now, but I'm at a loss as to how to abstract it so that it is not tied to spelling out each specific column name.
<?php
class Example extends DatabaseObject {
static protected $table_name = "data";
static protected $db_columns = ['id','column1', 'column2', 'column3', 'column4'];
public $id;
public $column1;
public $column2;
public $column3;
public $column4;
public function __construct($args=[]) {
$this->column1 = $args['column1'] ?? '';
$this->column2 = $args['column2'] ?? '';
$this->column3 = $args['column3'] ?? '';
$this->column4 = $args['column4'] ?? '';
}
public function attributes() {
$attributes = [];
foreach (static::$db_columns as $column) {
if ($column === "id") { continue; }
$attributes[$column] = $this->$column;
}
return $attributes;
}
public function save() {
// calls private methods to validate, sanitize, then create or update, acc. to context
// to create, it preps an sql statement based on a call to attributes()
}
}
?>
I'm not sure how far you want to take this. This is one way that you can abstract the data for a row.
You will notice that I use set and get functions to access the data.
class Example extends DatabaseObject {
static protected $table_name = "data";
static protected $db_columns = ['id', 'column1', 'column2', 'column3', 'column4'];
public $id;
protected $data = [];
public function __construct($args=[]) {
foreach(self::$db_columns as $columnName) {
if ( isset($args[$columnName]) ) {
$this->data[$columnName] = $args[$columnName];
}
}
}
public function set($columnName, $value)
{
if ( in_array($columnName, $this->data) && isset($this->data[$columnName]) ) {
$this->data[$columnName] = $value;
}
}
public function get($columnName)
{
if ( isset($this->data[$columnName]) ) {
return $this->data[$columnName];
}
return null;
}
public function attributes() {
$attributes = [];
foreach (static::$db_columns as $columnName) {
if ($column === "id") { continue; }
$attributes[$columnName] = $this->data[$columnName];
}
return $attributes;
}
public function save() {
// calls private methods to validate, sanitize, then create or update, acc. to context
// to create, it preps an sql statement based on a call to attributes()
}
}
You may need to look at the magic methods __get() and __set(). Basically, they are class methods that are triggered when trying to access a property that doesn't exist in your class, so you can have a fallback for them.
Please take a look at the documentation: https://www.php.net/manual/en/language.oop5.overloading.php#object.set
Okay the issue is something like this
I have a function in AController
public function index()
{
$store = Store::(query)(to)(rows)->first();
return view('store.index', compact('store'));
}
Now in the same controller I have another function
public function abc()
{
return view('store.abc');
}
Now to this function I also want to send the compact('store') to the view abc I can just add the query again in the abc() function but that would be lazy and make performance issues. Is there a way that I can access $store object in other functions too?
If I understand you correctly you want to access the same query from two places. So extract getting stores to another method like
private function store()
{
$minutes = 10; // set here
return Cache::remember('users', $minutes, function () {
return Store::(query)(to)(rows)->first();
});
}
Additionally I have cached the query. So it get executed once at a defiened time.
Then access it from other two methods like,
public function index()
{
$store = $this->store();
return view('store.index', compact('store'));
}
public function abc()
{
$store = $this->store();
return view('store.abc', compact('store'));
}
class StoreController extends Controller
{
public function index()
{
return view('admin.store',['data' => $this->getSetting()]);
}
public function getStoreData()
{
//get your data here, for example
$data = Store::where('status',1)->first();
//get all data
//$data = Store::all();
return ($data);
}
}
Try the following. Not testing but it should work for you.
class AController
{
public function getStore()
{
$store = Store::(query)(to)(rows)->first();
return compact('store');
}
public function index()
{
return view('store.index', $this->getStore());
}
public function abc()
{
return view('store.abc', $this->getStore());
}
}
So I have this controller that passes an associative array called $pagedata to the view. Inside this array are 3 more associative arrays, and the view renders 3 select elements with the array data as options. I want to sort the 3 arrays but I don't want to write sort 3 times here or add order_by into the query methods, because there are dozens of similar pages and I don't want to write hundreds of sort method calls. I was told I could solve this in the constructor. I was wondering if there's an OOP solution that lets me automatically sort all child arrays inside $pagedata.
class Sku extends CI_Controller {
protected $pagedata = array();
public function __construct()
{
parent::__construct();
$this->load->model('mc');
}
public function inventory()
{
$this->pagedata['class_ids'] = $this->mc->get_class_ids();
$this->pagedata['retail_readys'] = $this->mc->get_all_retail_ready();
$this->pagedata['statuses'] = $this->mc->get_all_status();
}
}
Edit:
I'm exploring using an ArrayObject or wrapping $pagedata in an object and watch for changes.
ok this will be painfull for codeigniter but yes a kind of solution
public function __construct()
{
parent::__construct();
$this->load->model('mc');
$class_ids = $this->mc->get_class_ids();
$class_ids = $this->sortAscending($class_ids, $key);
$this->pagedata['class_ids'] = $class_ids;
$retail_readys = $this->mc->get_all_retail_ready();
$class_ids = $this->sortAscending($class_ids, $key);
$this->pagedata['class_ids'] = $class_ids;
$statuses = $this->mc->get_all_status();
$statuses = $this->sortAscending($class_ids, $key);
$this->pagedata['statuses'] = $statuses;
}
function sortAscending($accounts, $key)
{
$ascending = function($accountA, $accountB) use ($key) {
if ($accountA[$key] == $accountB[$key]) {
return 0;
}
return ($accountA[$key] < $accountB[$key]) ? -1 : 1;
};
usort($accounts, $ascending);
return $accounts;
}
public function inventory()
{
// already get values
//$this->pagedata['class_ids'] = $this->mc->get_class_ids();
//$this->pagedata['retail_readys'] = $this->mc->get_all_retail_ready();
//$this->pagedata['statuses'] = $this->mc->get_all_status();
$this->load->view('index',$this->pagedata);
}
public function another_function()
{
// already get values
//$this->pagedata['class_ids'] = $this->mc->get_class_ids();
//$this->pagedata['retail_readys'] = $this->mc->get_all_retail_ready();
//$this->pagedata['statuses'] = $this->mc->get_all_status();
$this->load->view('another page',$this->pagedata);
}