Laravel Submodel Of an Model - php

In my Laravel application, I need submodels of the base ORM model, for specific types of item in my DB which is specified in 'type' column in database.
In my base model, I use this override for function newFromBuilder
// OVERRIDES
public function newFromBuilder($attributes = [], $connection = null)
{
$class = "\\App\\Models\\" . ucfirst($attributes->type);
if (class_exists($class)) {
$model = new $class();
} else {
$model = $this->newInstance([], true);
}
$model->setRawAttributes((array)$attributes, true);
$model->setConnection($connection ?: $this->getConnectionName());
$model->fireModelEvent('retrieved', false);
return $model;
}
but for some reason when i call save or update function in submodel nothing happen :( Each submodel should ingered save function of fase model s that right ? Could anybody help me to fix issue with save function ?
Base model:
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Carbon;
use App\Models\Devices;
use App\Models\Records;
use App\Models\Rooms;
use App\Helpers\SettingManager;
use App\Types\GraphPeriod;
use App\Types\PropertyType;
class Properties extends Model
{
protected $fillable = [];
protected $table = 'sh_properties';
protected $primaryKey = 'id';
public $period = GraphPeriod::DAY;
//OVERIDES
public function newFromBuilder($attributes = [], $connection = null)
{
$class = "\\App\\Models\\" . ucfirst($attributes->type);
if (class_exists($class)) {
$model = new $class();
} else {
$model = $this->newInstance([], true);
}
$model->setRawAttributes((array)$attributes, true);
$model->setConnection($connection ?: $this->getConnectionName());
$model->fireModelEvent('retrieved', false);
return $model;
}
//NEW RELATIONS
public function records()
{
return $this->hasMany(Records::class, 'property_id');
}
public function latestRecord()
{
return $this->hasOne(Records::class, 'property_id')->latestOfMany();
}
public function device()
{
return $this->belongsTo(Devices::class);
}
public function room()
{
return $this->belongsTo(Rooms::class);
}
public function settings()
{
if ($settings = SettingManager::getGroup('property-' . $this->id)) {
return $settings;
}
return false;
}
//FUNCTIONS
public function getLatestRecordNotNull()
{
return Records::where("property_id", $this->id)->where("value", "!=", null)->where("value", "!=", 0)->first();
}
//Virtual Values
use HasFactory;
//Add Function for mutator for vaue (vith units) and rav value
public function values()
{
$dateFrom = Carbon::now()->subDays(1);
switch ($this->period) {
case GraphPeriod::WEEK:
$dateFrom = Carbon::now()->subWeek(1);
break;
case GraphPeriod::MONTH:
$dateFrom = Carbon::now()->subMonth(1);
break;
case GraphPeriod::YEAR:
$dateFrom = Carbon::now()->subYear(1);
break;
}
return $this->hasMany(Records::class, 'property_id')->whereDate('created_at', '>', $dateFrom)->orderBy('created_at', 'DESC');
}
public function getAgregatedValuesAttribute($period = GraphPeriod::DAY)
{
$dateFrom = Carbon::now()->subDays(1);
$periodFormat = "%Y-%m-%d %hh";
switch ($this->period) {
case GraphPeriod::WEEK:
$dateFrom = Carbon::now()->subWeek(1);
$periodFormat = "%Y-%m-%d";
break;
case GraphPeriod::MONTH:
$dateFrom = Carbon::now()->subMonth(1);
$periodFormat = "%Y-%m-%d";
break;
case GraphPeriod::YEAR:
$dateFrom = Carbon::now()->subYear(1);
$periodFormat = "%Y-%m";
break;
}
$agregatedData = Records::select(['value', 'done', 'created_at'])
->selectRaw("DATE_FORMAT(created_at, ?) as period", [$periodFormat])
->selectRaw("ROUND(MIN(value), 1) AS min")
->selectRaw("ROUND(MAX(value), 1) AS max")
->selectRaw("ROUND(AVG(value), 1) AS value")
->where('property_id', $this->id)
->orderBy('created_at', 'DESC')
->groupBy('period');
$agregatedData->where('created_at', '>=', $dateFrom);
return $agregatedData->get();
}
public function last_value()
{
return $this->hasOne(Records::class, 'property_id', 'id')->latest();
}
//Virtual Values
//Virtual Values
/**
* Minimum value that property had in past.
*
* #return int
*/
public function getMaxValueAttribute()
{
if ($this->records) {
return $this->records->max("value");
}
return false;
}
/**
* Maximum value that property had in past.
*
* #return int
*/
public function getMinValueAttribute()
{
if ($this->records) {
return $this->records->min("value");
}
return false;
}
/**
* step value used to increment each value usually used for range type or thermostats, graphs also.
*
* #return int
*/
public function getStepValueAttribute()
{
if ($step = SettingManager::get('step', 'property-' . $this->id)) {
return ($step->value < 1 ? $step->value : 1);
}
return false;
}
/**
* max set value for prop
*
* #return int
*/
public function getMaxValueSettingAttribute()
{
if ($step = SettingManager::get('max', 'property-' . $this->id)) {
return ($step->value > 1 ? $step->value : 1);
}
return false;
}
/**
* min set value for prop
*
* #return int
*/
public function getMinValueSettingAttribute()
{
if ($step = SettingManager::get('min', 'property-' . $this->id)) {
return ($step->value > 1 ? $step->value : 1);
}
return false;
}
public function setValue($value)
{
$record = new Records;
$record->value = $value;
$record->property_id = $this->id;
$record->save();
return true;
}
}
Submodel
namespace App\Models;
use App\Models\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Humi extends Properties
{
protected $historyDefault = 90;
protected $unitsDefault = "%";
protected $iconDefault = "";
public function save(array $options = [])
{
// before save code
$result = parent::save($options); // returns boolean
// after save code
return $result; // do not ignore it eloquent calculates this value and returns this, not just to ignore
}
}
Thank you in advance for any suggestions or help :)

When a new instance is created with the $this->newInstance() function, the $model->exists property is set to true. I think you should do the same in your if statement as well. Otherwise it will try to create a new record in the database.
It might be a good idea to copy the rest of the function as well to avoid any other problems it may cause.
if (class_exists($class)) {
$model = new $class();
// Important
$model->exists = true;
$model->setTable($this->getTable());
$model->mergeCasts($this->casts);
} else {
$model = $this->newInstance([], true);
}

Related

Connecting method/function in laravel

I'm trying to create a class function which resembles how we used to fetch database listing and convert into a dropdown listing.
eg: DB::table()->where()->get()
what i would like to achieve in laravel custom class or through model is this
Dropdown::fetch()->toArray()
Dropdown::fetch()->toDropdown()
I tried to figure out how this can be done through google. But couldn't find any solution to it.
I'm using laravel 5.8
--
Edit - Sample Code added
Code tried:
namespace App\Http\Models;
use DB;
use Closure;
use BadMethodCallException;
use Illuminate\Support\Traits\Macroable;
use Illuminate\Database\Eloquent\Model;
class Dropdown extends Model
{
private $result = [];
private $default;
public function _cities(){
$tbl_cities = config("tables.TBL_meta_cities");
$result = DB::table($tbl_cities)->select('id', 'cityname')
->orderBy('id')->get()->toArray();
$this->result = $result;
}
public function _select(){
}
public function _list(){
return $this->result;
}
public function _setDefault($def=''){
}
public static function __callStatic($method, $parameters)
{
$action = '_'.$method;
if(method_exists(get_called_class(), $action))
self::$action(...$parameters);
else echo 'not found';
}
public function __call($method, $parameters)
{
$action = '_'.$method;
if(method_exists($get_called_class(), $action))
self::$action(...$parameters);
else echo 'not found';
}
}
and i tried
Dropdown::cities()->list()
but ended with bugs
Well i figured it out myself.
class Dropdown extends Model
{
private static $result = [];
private function getCities(){
$result = City::select('id', 'cityname')
->orderBy('id')->get()->toArray();
self::$result = $result;
}
public function toArray(){
return self::$result;
}
public function toDropdown(){
// Do the dropdown works
}
/**
* Dynamically handle calls to the class.
*
* #param string $method
* #param array $parameters
* #return mixed
*
* #throws \BadMethodCallException
*/
public function __callMethod($method, $parameters){
// Check with inclusive
$class = get_called_class();
$avail = false;
$action = '';
// Check method availability - direct
if(!$avail){
$action = $method;
$avail = method_exists($class, $action);
}
// Check method 2
if(!$avail){
$action = 'get'.ucwords($method);
$avail = method_exists($class, $action);
}
if($avail){
// Call the method
$return = self::$action(...$parameters);
if(!empty($return)) return $return;
} else {
// Throw error if method not found
throw new BadMethodCallException("No such method exists: $name");
}
return new self;
}
public static function __callStatic($method, $parameters){
return (new self)->__callMethod($method, $parameters);
}
public function __call($method, $parameters){
return (new self)->__callMethod($method, $parameters);
}
}
All i need to do is return new self which does the trick instead of return $this so that the trailing function can be called easily.
Now i can able to call that function like this
Dropdown::cities()->toArray();
Reference:
https://stackoverflow.com/a/41631711/1156493
Thank you #Joseph for your time & support.

Array to string conversion error -Slim Twig-

Setup: I have a slim application and I pulled in Illuminate DB and Twig view.
if (!$validator->passed()) {
$errors = $validator->errors();
$users = User::all();
return $this->view($response, 'auth.login', compact('errors','users'));
}
Problem: When I run the above code, I am able to retrieve the users variable in my view, but the errors variable throws the following error.
Notice: Array to string conversion in /Users/davidchen/Documents/sites/slim.com/vendor/twig/twig/lib/Twig/Environment.php(378) : eval()'d code
on line
70 Array
The errors variable returns a multidimensional array, below you'll find the result that I get from print_r($errors).
Array (
[username] => Array (
[0] => username already exists
)
[password] => Array (
[0] => password must consist of at least 6 characters
)
)
Here are my related project files:
Twig Setup File (app.php)
$c = $app->getContainer();
$capsule = new \Illuminate\Database\Capsule\Manager;
$capsule->addConnection($config['config']['db']);
$capsule->setAsGlobal();
$capsule->bootEloquent();
$c['db'] = function($c) use ($capsule){
return $capsule;
};
$c['view'] = function($c){
$options['cache']=false;
$view = new \Slim\Views\Twig(__DIR__."/../views", $options);
$view->addExtension(new \Slim\Views\TwigExtension(
$c->router,
$c->request->getUri()
));
$view->getEnvironment()->addGlobal('flash', $c->flash);
return $view;
};
$c['flash'] = function($c){
return new Slim\Flash\Messages();
};
Validator Class
namespace App\Models\Auth;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Capsule\Manager as DB;
use Carbon\Carbon;
use DateTime;
class Validator extends Model
{
protected $field_name,
$data,
$errors = [];
/*
* Main validator
*/
public function __construct($request, $fields = []){
$data = $request->getParams();
$this->data = $data;
foreach ($fields as $field => $constraints) {
$this->field_name = $field;
if (isset($data[$field])) {
$field_value = $data[$field];
foreach (explode("|", $constraints) as $constraint) {
$obj = explode(":", $constraint);
$function_name = $obj[0];
if (isset($obj[1])) {
if(method_exists($this, $function_name))
{
$this->$function_name($obj[1],$field_value);
}
}
}
}else{
if (strpos($constraints, 'required') !== false) {
$validator->report($validator->field_name.' field is requried');
}
}
}
return $this;
}
/*
* Object Interface Methods
*/
private function report($message){
$this->errors[$this->field_name][]= $message;
}
public function errors(){
return $this->errors;
}
public function passed(){
if (!count($this->errors)) {
return true;
}
}
/*
* Validation Rules
*/
public function max($length,$value){
if (strlen($value)>$length) {
$this->report("{$this->field_name} must consist of less than {$length} characters");
}
}
public function min($length,$value){
if (strlen($value)<$length) {
$this->report("{$this->field_name} must consist of atleast {$length} characters");
}
}
public function distinct($tableName,$value){
if (DB::table($tableName)->where($this->field_name, $value)->exists()) {
$this->report("{$this->field_name} already exists");
}
}
public function date($format,$date){
if (!preg_match("/\d{4}-\d{2}-\d{2}\b/",$date)) {
$this->report("incorrect {$this->field_name} values");
}else{
$d = DateTime::createFromFormat($format, $date);
if ($d && $d->format($format) !== $date) {
$this->report("{$this->field_name} format should be {$format}");
}
}
}
public function match($matchField,$value){
if (isset($this->data[$matchField])) {
$valueTwo = $this->data[$matchField];
if ($value !== $valueTwo) {
$this->report("{$this->field_name} does not match {$matchField}");
}
}else{
$this->report("{$matchField} is required");
}
}
public function format($type,$value){
switch ($type) {
case 'noWhiteSpace':
if (preg_match("/\s/",$value)) {
$this->report("{$this->field_name} may not contain any spaces");
}break;
case 'alpha':
if (preg_match("/[^a-zA-Z]/",$value)) {
$this->report("{$this->field_name} may only contain letters");
}break;
case 'alphaNum':
if (preg_match("/[^a-zA-Z0-9]/",$value)) {
$this->report("{$this->field_name} may only contain letters");
}break;
case 'email':
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
$this->report("in correct {$this->field_name} format");
}break;
default:
# code...
break;
}
}
}
Base Controller
namespace App\Controllers;
/**
*
*/
class Controller
{
protected $c;
function __construct($container)
{
$this->c = $container;
}
public function view($response, $path,$variables = []){
$this->c->view->render($response, str_replace(".","/",$path).".twig", $variables);
}
public function pathFor($routeName,$data = []){
return $this->c->router->pathFor($routeName,$data);
}
}
Auth Controller
namespace App\Controllers\Auth;
use App\Models\User\User;
use App\Controllers\Controller;
use App\Models\Auth\Validator;
/**
*
*/
class AuthController extends Controller
{
public function getLogin($request, $response){
return $this->view($response, 'auth.login');
}
public function postLogin($request, $response){
$validator = new Validator($request,[
'username'=>'required|min:3|max:64|format:alphaNum|distinct:users',
'password'=>'required|min:6|max:64|',
]);
if (!$validator->passed()) {
$errors = $validator->errors();
$users = User::all();
return $this->view($response, 'auth.login', compact('errors','users'));
}
return $this->view($response, 'home.login');
}
}
login.twig file
login.twig file
Hope one of you can shed some light on this problem. I've been struggling with this all morning.
You could try to loop over each item in a sequence. For example, to display a list of users provided in a variable called users:
<h1>Members</h1>
<ul>
{% for user in users %}
<li>{{ user.username|e }}</li>
{% endfor %}
</ul>
Read more

PHP static property with extending class overwritten

Please, could you help me a bit with my problem?
I have class called Translateable and then clasess Article and Banner, which extend this class.
Problem occurs, when I do this:
$article = (new Article)->find(15);
$banner = (new Banner)->find(1);
$articleTrans = $article->trans(); // method trans is method from Translateable
When I call $article->trans(); I expect output like this:
App\Models\ArticleTrans
Article
but it return this:
App\Models\ArticleTrans
Banner
First row is ok, but the second one if bad and I don't know, how to solve this problem.
I need to have $instance stored as static property.
Could you give me you help?
class Translateable extends Model {
static $transLang = null;
static $transClass = null;
static $instance = null;
public function __construct(array $attributes = array()) {
static::$transLang = App::getLocale();
parent::$transClass = static::$transClass;
parent::$instance = static::$instance;
parent::__construct($attributes);
}
/**
* get items trans
*
* #param null $lang
* #return mixed
*/
public function trans($lang = null) {
if($lang == null) {
$lang = static::$transLang;
}
echo static::$transClass;
echo class_basename(static::$instance);
die();
}
public static function find($primaryKeyVal, $columns = []) {
$tci = new static::$transClass;
$item = static::withTrans()->where(static::$instance->getTable() . '.' . static::$instance->primaryKey, '=', $primaryKeyVal)->where($tci->getTable() . '.lang', '=', static::$transLang)->first();
return $item;
}
}
class Article extends Translateable {
static $transClass = 'App\Models\ArticleTrans';
public function __construct(array $attributes = array()) {
parent::$transClass = static::$transClass;
parent::$instance = $this;
parent::__construct($attributes);
}
}
class Banner extends Translateable {
static $transClass = 'App\Models\BannerTrans';
public function __construct(array $attributes = array()) {
parent::$transClass = static::$transClass;
parent::$instance = $this;
parent::__construct($attributes);
}
}

PHP object property overritten

I'm solving quite strange problem which I'm facing for the first time.
I have some main Class with property static::$someProperty and I extend with this class other classes, e.g. ClassA and ClassB.
Problem is now, when I load
$classA = ClassA
and set there
static::$someProperty = "ClassA"
and echo this value, it works fine and return "ClassA" but then I also load
$classB = ClassB
and set
static::$someProperty = "ClassB"
and when I
echo static::$someProperty
in $classA now, there is value "ClassB".
Do you know, how to solve this problem? Probably it is connected with static, but I don't now, what to do with this.
class Translateable extends Model{
public static $transLang;
public static $transClassInstance;
public static $instance;
public $transInstance = null;
public function __construct(array $attributes = array()) {
self::$transLang = App::getLocale();
$tcName = static::$instance->transClass;
static::$transClassInstance = new $tcName;
parent::__construct($attributes);
}
/**
* add trans to the item
*
* #return mixed
*/
public static function withTrans($lang = null) {
if($lang == null) {
$lang = static::$transLang;
}
return static::join(static::$transClassInstance->getTable(), function ($join) use ($lang) {
$join->on(static::$instance->getTable() . '.' . static::$instance->primaryKey, '=', static::$transClassInstance->getTable() . '.' . static::$instance->primaryKey)->where(static::$transClassInstance->getTable() . '.lang', '=', $lang);
})->where(static::$transClassInstance->getTable() . '.lang', '=', $lang)
;
}
}
class Nested extends Translateable{
// protected $lft, $lvl, $rgt, $parent_ID;
public static $transClassInstance;
public static $transLang;
public function __construct(array $attributes = array()) {
self::$transLang = App::getLocale();
$tcName = static::$instance->transClass;
static::$transClassInstance = new $tcName;
parent::$instance = $this;
parent::__construct($attributes);
}
/**
*
* get $this item child
*
* #return null
*/
public function getChilds() {
$primaryKeyName = $this->primaryKey;
$parent_id = $this->$primaryKeyName;
// here is echo PageTrans instead of ProductCategoryTrans
echo static::$transClassInstance->getTable().'<br/>';
echo static::$transClassInstance->getTable() . '.lang'.'<br/>';
$query = static::where('parent_ID', '=', $parent_id)->where(static::$transClassInstance->getTable() . '.lang', '=', static::$transLang);
echo $query->toSql();
$this->generateItemsQuery($query);
$query->orderBy('lft', 'ASC');
$categories = $query->get();
return $categories;
}
}
class ProductCategory extends Nested{
public $transClass = 'App\Models\ProductCategoryTrans';
public function __construct(array $attributes = array()) {
static::$instance = $this;
parent::__construct($attributes);
}
}
class Page extends Nested{
public $transClass = 'App\Models\PageTrans';
public function __construct(array $attributes = array()) {
static::$instance = $this;
parent::__construct($attributes);
}
}
Example usage:
// find product category with ID == 1
$productCategory = (new ProductCategory)->find(1); // "ClassA"
// get some page...
$page = (new Page)->find(1); // find page with ID == 1 // "ClassB"
// get childs of loaded category
$categoryChilds = $productCategory->getChilds(); // get this category
Try to use self in classA and classB
self::$someProperty = 'test';

Laravel replace property with another if it is 0

I have two columns, price and offer_price.
When I call $products->price, I would like it to bring back the offer_price if it is above 0, if not, return the price.
My modal:
class Product extends Eloquent {
protected $table = 'products';
public function type() {
return $this->hasOne('ProductTypes', 'id', 'type_id');
}
public function brand()
{
return $this->hasOne('ProductBrands', 'id', 'brand_id');
}
public function image() {
return $this->hasMany('ProductImages');
}
public function toArray() {
$ar = $this->attributes;
$ar['type'] = $this->type;
$ar['brand'] = $this->spec_brand;
$ar['price'] = $this->price;
return $ar;
}
public function getSpecBrandAttribute() {
$brand = $this->brand()->first();
return (isset($brand->brand) ? $brand->brand : '');
}
public function getPriceAttribute() {
$price = $this->price;
return (isset($price->price) ? $price->price : '');
}
}
I'm trying to use it here:
$brands = array();
$prices = array();
$out = '';
foreach ($products as $product) {
$brands[$product->brand->id] = $product->brand->brand;
$prices[] = $product->price;
}
You're on the right track. You can find your data in the $attributes array member of your model, rather than distinct member variables in your model.
How about:
public function getPriceAttribute()
{
$which = (0 < $this->attributes['offer_price'] ? 'offer_price' : 'price');
return $this->attributes[$which];
}
Though I would recommend a name other than 'price', unless you completely want to mask 'price' from the usual Laravel interaction. Perhaps $modal->best_price?

Categories