Laravel 4 - Using Eloquent Models in a custom library class - php

I made a library class that I am using for some common functions not provided by Laravel. It's been loaded into /config/app.php under the 'aliases' array, so that shouldn't be the problem.
When I call a method from my class ("InfoParse"), my conroller returns a blank page. I think this has to do with the fact that I'm calling a method from the library which uses Eloquent to interface with the database. I tried adding
use Illuminate\Database\Eloquent\Model;
to the top of the file, but that didn't help either.
Is there a specific way I should be setting up my class file so I can use either the DB:: class or Eloquent class?
Below is the function in question:
/**
* Check to see if this student is already recorded in our student table.
* If not, add the entry, then return true.
* #param int $cwid
* #return boolean
*/
public static function checkStudentTableRecords($cwid)
{
if(Student::where('cwid', '=', $cwid)->count() != 0)
{
return TRUE;
}
else
{ ##insert the student into our student table
$studentInfo = self::queryInfoFromCWID($cwid);
$studentEntry = new Student;
$studentEntry->cwid = $cwid;
$studentEntry->fName = $studentInfo['fName'];
$studentEntry->lName = $studentInfo['lName'];
$studentEntry->email = $studentInfo['email'];
$studentEntry->save();
return TRUE;
}
}
(note: the self::queryInfoFromCWID() function is calling a function defined earlier in the class)

After some investigation, it turns out I need to format my Eloquent Model calls like this:
if(\Student::where('cwid', '=', $cwid)->count() != 0)
...
$studentEntry = new \Student;
The backslash is necessary to avoid namespace collision within the Laravel4 application.

Related

Laravel Eloquent - Model extends other model

I have a question about extending my own Models eloquent.
In the project I am currently working on is table called modules and it contains list of project modules, number of elements of that module, add date etc.
For example:
id = 1; name = 'users'; count = 120; date_add = '2007-05-05';
and this entity called users corresponds to model User (Table - users) so that "count" it's number of Users
and to update count we use script running every day (I know that it's not good way but... u know).
In that script is loop and inside that loop a lot of if statement (1 per module) and inside the if a single query with count. According to example it's similar to:
foreach($modules as $module) {
if($module['name'] == 'users') {
$count = old_and_bad_method_to_count('users', "state = 'on'");
}
}
function old_and_bad_method_to_count($table, $sql_cond) {}
So its look terrible.
I need to refactor that code a little bit, because it's use a dangerous function instead of Query/Builder or Eloquent/Model and looks bad.
I came up with an idea that I will use a Models and create Interface ElementsCountable and all models that do not have an interface will use the Model::all()->count(), and those with an interface will use the interface method:
foreach ($modules as $module) {
$className = $module->getModelName();
if($className) {
$modelInterfaces = class_implements($className);
if(isset($modelInterfaces[ElementsCountable::class])) {
/** #var ElementsCountable $className */
$count = $className::countModuleElements();
} else {
/** #var Model $className */
$count = $className::all()->count();
}
}
}
in method getModelName() i use a const map array (table -> model) which I created, because a lot of models have custom table name.
But then I realize that will be a good way, but there is a few records in Modules that use the same table, for example users_off which use the same table as users, but use other condition - state = 'off'
So it complicated things a little bit, and there is a right question: There is a good way to extends User and add scope with condition on boot?
class UserOff extends User
{
protected static function boot()
{
parent::boot();
static::addGlobalScope(function (Builder $builder) {
$builder->where('state', '=', 'off');
});
}
}
Because I have some concerns if this is a good solution. Because all method of that class NEED always that scope and how to prevent from method withoutGlobalScope() and what about other complications?
I think it's a good solution to create the UserOff model with the additional global scope for this purpose.
I also think the solution I would want to implement would allow me to do something like
$count = $modules->sum(function ($module) {
$className = $module->getModelName();
return $className::modulesCount();
}
I would create an interface ModulesCountable that mandates a modulesCount() method on each of the models. The modulesCount() method would return either the default count or whatever current implementation you have in countModuleElements().
If there are a lot of models I would probably use a trait DefaultModulesCount for the default count, and maybe the custom version too eg. ElementsModuleCount if that is consistent.

Extending DB facade Laravel

I would like to convert a timestamp and have some other values related to it. My question is how I can introduce my own method like DB::raw() that appends everything to the current select values.
So, for instance, for something like this
$user = DB::table('users')
->select('*', DB::timestamp('timestamp_column', 'convert_timezone', 'called_as'))
->where('id', 1)->first();
Let's assume that I am trying to get the value for created_at column and it's called as converted_created_at and it should return something like below.
{
id: 1,
name:'John Doe',
converted_created_at: {
'utc_time': 'created_at value as that is in utc by default',
'converted_time': 'timestamp converted into user timezone',
'diff': '10 hours ago' // difference between created_at and current timestamp
}
}
So, how do I introduce my own method that does this?
You can take example of any SQL database as you wish.
I know I can do that with Model but I wanted to see how to approach this problem using a facade.
Thank you in advance for your help.
First look here: https://stackoverflow.com/a/40615078/860099 - Try this Extend DB facade:
namespace App\Facades;
use Illuminate\Support\Facades\DB as DBBase;
class DB extends DBBase {...}
and in config/app.php change
'DB' => Illuminate\Support\Facades\DB::class,
to
'DB' => App\Facades\DB::class,`
(i write code from head)
Alternative:
You can easily create helper class eg. DBTools witch static methods and inside that methods you will use DB and construct proper query. And use it like that DBTools::yourMethod(...)
As argument to that method you can give... QUERY here is example of calling this method
DBTools::yourMethod(User::query())->first();
and inside you can easyily manipulate that query and return updated version.
ALTERNATIVE: If your goal is to add some new filed in Model (json) that not exist in db but is generated then you can use $appends (look: mutators and appends)
class User extends Model
{
protected $appends = ['converted_created_at'];
...
public function getConvertedCreatedAtAttribute() {
return ...; // return generated value from other fields/sources
}
Thanks to #kamil for showing me the way.
I am writing an answer in case anyone in the future finds this helpful.
I have come up with my own method that helps to convert timezone easily without writing too much code inside select query for DB facade for PostgreSQL.
I have created a file like this now.
<?php
namespace App\Custom\Facade;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
class DBTools extends DB
{
/**
* Convert a timestamp
* #param $timestamp - timestamp to be converted
* #param bool $insideRaw - if this helper method is getting used inside DB::raw() method
* #param null $timezone
* #param null $format - time format
* #param null $calledAs - column to called as
* #return \Illuminate\Database\Query\Expression|string
*/
public static function convertTime($timestamp, $insideRaw = false, $timezone = null, $format = null, $calledAs = null)
{
if (Auth::check()) {
if (!$timezone)
$timezone = Auth::user()->timezone;
if (!$format)
$format = Auth::user()->time_format;
}
$query = "to_char($timestamp at time zone '$timezone', '$format')" . ($calledAs ? " as $calledAs" : '');
if (!$insideRaw) {
return DB::raw($query);
}
return $query;
}
}
Now this can be easily be called inside select for DB facade or inside DB::raw() in case you're handling much more complicated query.
Hope this helps someone.

How do I implement Gravatar in Laravel?

What's the fastest way to implement Gravatar URLs in Laravel? I have a mandatory email address field, but I don't want to create a new column for Gravatars, and I'd prefer to use the native Auth::user() attributes.
Turns out, you can use a Laravel mutator to create attributes that don't exist in your model. Assuming you have a User model with a mandatory email column in the corresponding users table, just stick this in your User model:
public function getGravatarAttribute()
{
$hash = md5(strtolower(trim($this->attributes['email'])));
return "http://www.gravatar.com/avatar/$hash";
}
Now when you do this:
Auth::user()->gravatar
You'll get the gravatar.com URL you're expecting. Without creating a gravatar column, variable, method, or anything else.
Expanding on Wogan's answer a bit...
Another example using a Trait:
namespace App\Traits;
trait HasGravatar {
/**
* The attribute name containing the email address.
*
* #var string
*/
public $gravatarEmail = 'email';
/**
* Get the model's gravatar
*
* #return string
*/
public function getGravatarAttribute()
{
$hash = md5(strtolower(trim($this->attributes[$this->gravatarEmail])));
return "https://www.gravatar.com/avatar/$hash";
}
}
Now on a given model (i.e. User) where you want to support Gravatar, simply import the trait and use it:
use App\Traits\HasGravatar;
class User extends Model
{
use HasGravatar;
}
If the model doesn't have an email column/attribute, simply override the default by setting it in the constructor of your model like so:
public function __construct() {
// override the HasGravatar Trait's gravatarEmail property
$this->gravatarEmail = 'email_address';
}

CakePHP: Managing table names in a controller with a single variable

In controller A, I load a model not associated with this controller. I'm interested in managing the model name of controller B with a single variable, so I don't have to manually change many lines if the table/model B's name changes.
For example below is the controller A's code:
public $modelBName = 'ModelB';
public function controller_a_function() {
$this->loadModel($this->modelBName); // I use the variable here for model B
$this->ModelB->model_b_function(); // COMMENT #1
}
Question:
For the line commented "COMMENT #1," how do I use the variable name instead of explicitly written out word 'ModelB'? This line appears multiple times throughout the code, and I would like to use the variable $modelBName if possible. ModelB will likely not change, but if it does for some reason, it would be nice to just change one variable instead of editting multiple lines.
The simple answer; use this:
$this->{$this->modelBName}->find('all');
Note the curly brackets {} around the property name. more information can be found in the manual;
http://php.net/manual/en/language.variables.variable.php
A cleaner approach may be a sort of 'factory' method;
/**
* Load and return a model
*
* #var string $modelName
*
* #return Model
* #throws MissingModelException if the model class cannot be found.
*/
protected function model($modelName)
{
if (!isset($this->{$modelName})) {
$this->loadModel($modelName);
}
return $this->{$modelName};
}
Which can be used like this;
$result = $this->model($this->modelBName)->find('all');
debug($result);
And, if you don't want to specify the model, but want it to return a '$this->modelBName' automatically;
/**
* Load and return the model as specified in the 'modelBName' property
*
* #return Model
* #throws MissingModelException if the model class cannot be found.
*/
protected function modelB()
{
if (!isset($this->{$this->modelBName})) {
$this->loadModel($this->modelBName);
}
return $this->{$this->modelBName};
}
Which can be used like this:
$result = $this->modelB()->find('all');
debug($result);
I think you are confused between model name and table name. You can set a model to use a different database table by using the $useTable property, for example:
class User extends AppModel {
public $useTable = 'users_table'; // Database table used
}
class Product extends AppModel {
public function foo() {
$this->loadModel('User');
$this->User->find('all');
}
}
You should never need to change the name of the model, and if the name of the database table changes you can simply update the $useTable property in the model.

Activerecord-association: create new object (find class)

I have an model with a relation, and I want to instantiate a new object of the relations type.
Example: A person has a company, and I have a person-object: now I
want to create a company-object.
The class of the companyobject is defined in the relation, so I don't think I should need to 'know' that class, but I should be able to ask the person-object to provide me with a new instance of type company? But I don't know how.
This is -I think- the same question as New model object through an association , but I'm using PHPActiveRecord, and not the ruby one.
Reason behind this: I have an abstract superclass person, and two children have their own relation with a type of company object. I need to be able to instantiate the correct class in the abstract person.
A workaround is to get it directly from the static $has_one array:
$class = $this::$has_one[1]['class_name'];
$company = new $class;
the hardcoded number can of course be eliminated by searching for the association-name in the array, but that's still quite ugly.
If there is anyone who knows how this is implemented in Ruby, and how the phpactiverecord implementation differs, I might get some Ideas from there?
Some testing has revealed that although the "search my classname in an array" looks kinda weird, it does not have any impact on performance, and in use it is functional enough.
You can also use build_association() in the relationship classes.
Simplest way to use it is through the Model's __call, i.e. if your relation is something like $person->company, then you could instantiate the company with $company = $person->build_company()
Note that this will NOT also make the "connection" between your objects ($person->company will not be set).
Alternatively, instead of build_company(), you can use create_company(), which will save a new record and link it to $person
In PHPActiveRecord, you have access to the relations array. The relation should have a name an you NEED TO KNOW THE NAME OF THE RELATIONSHIP/ASSOCIATION YOU WANT. It doesn't need to be the classname, but the classname of the Model you're relating to should be explicitly indicated in the relation. Just a basic example without error checking or gritty relationship db details like linking table or foreign key column name:
class Person extends ActiveRecord\Model {
static $belongs_to = array(
array('company',
'class_name' => 'SomeCompanyClass')
);
//general function get a classname from a relationship
public static function getClassNameFromRelationship($relationshipName)
foreach(self::$belongs_to as $relationship){
//the first element in all relationships is it's name
if($relationship[0] == $relationshipName){
$className = null;
if(isset($relationship['class_name'])){
$className = $relationship['class_name'];
}else{
// if no classname specified explicitly,
// assume the clasename is the relationship name
// with first letter capitalized
$className = ucfirst($relationship);
}
return $className
}
}
return null;
}
}
To with this function, if you have a person object and want an object defined by the 'company' relationship use:
$className = $person::getClassNameFromRelationship('company');
$company = new $className();
I'm currently using below solution. It's an actual solution, instead
of the $has_one[1] hack I mentioned in the question. If there is a
method in phpactiverecord I'm going to feel very silly exposing
msyelf. But please, prove me silly so I don't need to use this
solution :D
I am silly. Below functionality is implemented by the create_associationname call, as answered by #Bogdan_D
Two functions are added. You should probably add them in the \ActiveRecord\Model class. In my case there is a class between our classes and that model that contains extra functionality like this, so I put it there.
These are the 2 functions:
public function findClassByAssociation($associationName)
Called with the name of the association you are looking for.
Checks three static vars (has_many,belongs_to and has_one) for the association
calls findClassFromArray if an association is found.
from the person/company example: $person->findClassByAssociation('company');
private function findClassFromArray($associationName,$associationArray)
Just a worker-function that tries to match the name.
Source:
/**
* Find the classname of an explicitly defined
* association (has_one, has_many, belongs_to).
* Unsure if this works for standard associations
* without specific mention of the class_name, but I suppose it doesn't!
* #todo Check if works without an explicitly set 'class_name', if not: is this even possible (namespacing?)
* #todo Support for 'through' associations.
* #param String $associationName the association you want to find the class for
* #return mixed String|false if an association is found, return the class name (with namespace!), else return false
* #see findClassFromArray
*/
public function findClassByAssociation($associationName){
//$class = $this::$has_one[1]['class_name'];
$that = get_called_class();
if(isset($that::$has_many)){
$cl = $this->findClassFromArray($associationName,$that::$has_many);
if($cl){return $cl;}
}
if(isset($that::$belongs_to)){
$cl = $this->findClassFromArray($associationName,$that::$belongs_to);
if($cl){return $cl;}
}
if(isset($that::$has_one)){
$cl = $this->findClassFromArray($associationName,$that::$has_one);
if($cl){return $cl;}
}
return false;
}
/**
* Find a class in a php-activerecord "association-array". It probably should have a specifically defined class name!
* #todo check if works without explicitly set 'class_name', and if not find it like standard
* #param String $associationName
* #param Array[] $associationArray phpactiverecord array with associations (like has_many)
* #return mixed String|false if an association is found, return the class name, else return false
* #see findClassFromArray
*/
private function findClassFromArray($associationName,$associationArray){
if(is_array($associationArray)){
foreach($associationArray as $association){
if($association['0'] === $associationName){
return $association['class_name'];
}
}
}
return false;
}

Categories