Custom function in model - php

I need to generate next number based on month and year. Is this proper way to do it or can it be done differently?
class Foo extends Model
{
public function getNextNumber()
{
$result = DB::select('SELECT COALESCE(MAX(number), 0) + 1 AS number FROM foo AS t2 WHERE MONTH(generated_at) = MONTH(CURDATE()) AND YEAR(generated_at) = YEAR(CURDATE())');
return head($result)->number;
}
}
When create a new record:
$foo = new Foo();
$foo->template = 'template1';
$foo->number = $foo->getNextNumber();
$foo->generated_at = DB::raw('NOW()');
$foo->save();

The query itself is fine as long as you don't have too many records in your table - otherwise I'd follow Joel's suggestion and store current number for given year and month in separate table like
Numbers(year, month, number).
If the Laravel code you provided is something you want to run for every model you create, so I'd suggest using Eloquent model events for that:
//Foo.php
protected static function boot() {
parent::boot();
static::creating(function($foo) {
$foo->number = $foo->getNextNumber();
$foo->generated_at = DB::raw('NOW()');
});
}
You could also make the getNextNumber method static so that you don't need an object to get the next number.

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.

Symfony2 Repository Query

I have a relationship many to many table progvol, table users, table flights
And a one-to-many table users relation rammasage
In the table rammassage I have a date field and in the table flights I have a start date I ve compare these two dates
So I write a sql query to know the date of the user of the vol table.
First j fai ds my controller to get the current user $ user = $ this-> getUser ();
And in my repositroy i did
<?php
namespace PfeBundle\Repository;
use Doctrine\ORM\EntityRepository;
/**
* programmevolRepository
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class programmevolRepository extends EntityRepository
{
public function getOnlyActive ($valeur,$vol)
{
$qb = $this->createQueryBuilder('a');
$qb->where('a.users = :valeur'); // means id_user of table programmevol = $valeur($user->getId());
$qb->where('a.vols = :vol'); // means id_vol of table vol = $vol (une instancedelaclassevol->getId());
$qb->where('vol' , $vol);
$qb->setParameter('valeur', $valeur);
return $qb->getQuery()->getOneOrNullResult();
}
}
in My controller i did
$comp = new CompanyEvents();
$user = $this->get('security.token_storage')->getToken()->getUser();
$userprogvol = $em->getRepository('PfeBundle:programmevol')->getOnlyActive($user->getId(),$comp->getId());
Now I have a $ userprogvol table in which I have the flight id of the programvol table + the id of the current user
Now I want to do (if $ userprog-> flights (id_vol) == $ comp-> id_vol) $ var = $ comp-> getDebt (); But $ userprogvol is an array ??
How can i do this please? Is there another trick (return type of a query is object) ??
well if it is an array you could just iterate it
foreach ($userprog->flights() as $flight) {
if($flight->getId() == $comp->getId()){
//...
}
}
because your variable and property naming is totaly unclear to me, this is just for let you get an idea
and it is not a simple array but an doctrine arrayCollection
so here you have a list of available methods
http://www.doctrine-project.org/api/common/2.3/class-Doctrine.Common.Collections.ArrayCollection.html
maybe $userprog->flights()->contains($element)
is what youre really after

Laravel-eloquent: Call to undefined method Illuminate\Database\Eloquent\Collection::where()

I have two models in many-to-one relationship:
class Meal extends \Eloquent {
/**
* public Integer $id; - primary key
* public String $name;
*/
protected $fillable = array('id','name');
public function mealProperties()
{
return $this->hasMany('MealProperty');
}
}
class MealProperty extends \Eloquent {
/**
* public Integer $id; - primary key
* public Integer $meal_id;
*/
protected $fillable = array('id','meal_id');
public function meal()
{
return $this->belongsTo('Meal', 'meal_id');
}
}
if I ask for first meal first mealProperty everything go fine:
$mealProp = Meal::first()->mealProperties->first();
but if I ask for mealProperty with specific id of first meal this way:
$mealProp = Meal::first()->mealProperties->where('id','=','1')->first();
I get this error:
Call to undefined method Illuminate\Database\Eloquent\Collection::where()
I google what I'm doing wrong two hours, but still nothing.
If I can't use where method, what is possible way to get specific mealProperty?
Thank you for help!
UPDATE for Laravel 5:
Since v5 release there is a method where on the Support\Collection object, so this question/answer becomes irrelevant. The method works exactly like filter, ie. returns filtered collection straight away:
$mealProp = Meal::first()->mealProperties->where('id','=','1'); // filtered collection
// that said, this piece of code is perfectly valid in L5:
$mealProp = Meal::first()->mealProperties->where('id','=','1')->first();
You must distinguish Laravel behaviour:
(dynamic property) Eloquent Collection or Model
$meal->mealProperties
Relation Object
$meal->mealProperties()
Now:
// mealProperties is Eloquent Collection and you call first on the Collection here
// so basically it does not affect db query
$mealProp = Meal::first()->mealProperties->first();
// here you try to add WHERE clause while the db query is already called
$mealProp = Meal::first()->mealProperties->where('id','=','1')->first();
// So this is what you want to do:
$mealProp = Meal::first()->mealProperties()->where('id','=','1')->first();
You may try this:
$mealProop1 = Meal::first()->mealProperties->find(1); // id = 1
Or something like this:
$mealProops = Meal::first()->mealProperties;
$mealProop5 = $mealProops->find(5); // id = 5
$mealProop7 = $mealProops->find(7); // id = 7
Instead of this:
$mealProp = Meal::first()->mealProperties->where('id','=','1')->first();
Also, following should work:
$mealProp = Meal::first()->mealProperties->first();

Laravel Interface to multiple models

I'm writing a system around an existing database structure using Laravel 4.1. The current system is based around two websites which use their own table, a and b, both of which are identical. This is an unavoidable problem until we rewrite the other system.
I need to be able to query both tables at the same time using Eloquents Query Builder, so I may need to get a list of rows from both tables, or INSERT or UPDATE from either at any time.
Currently we have a model for both tables, but no way to link between them and implement the missing methods such as all or find.
Our thought is to have an Interface which will bind these results together, however we're not sure how to go about this at all.
<?php
interface HotelInterface {
public function all();
public function find();
}
use Illuminate\Database\Model;
class Hotel implements HotelInterface {
}
?>
Is all we have so far.
I asked on the Laravel forums and got the answer I was looking for! I'm reposting here incase.
What we're actually after is a Repository which would look like this:
<?php
class HotelRepository {
public $A;
public $B;
public function __construct(A $A, B $B) {
$this->A = $A;
$this->B = $B;
}
public function find($iso = NULL, $hotelid = NULL) {
$A = $B = NULL;
if($iso !== NULL) {
$A = $this->A->where('country', $iso);
$B = $this->B->where('country', $iso);
if($hotelid !== NULL) {
$A = $A->where('id', $hotelid);
$B = $B->where('id', $hotelid);
}
}
if($hotelid !== NULL) {
if($A->first()) {
return $A->first();
}
if($B->first()) {
return $B->first();
}
}else{
return $A->get()->merge($B->get());
}
}
public function all() {
$aCollection = $this->A->all();
$bCollection = $this->B->all();
return $aCollection->merge($bCollection);
}
}
Now in the controller where I want to call this, I just add:
<?php
class HomeController extends BaseController {
public function __construct(HotelRepository $hotels) {
$this->hotels = $hotels;
}
}
And I can now use $this->hotels to access the find and all method that I created.
If the tables are identical you should only need one model, the connection is the only thing that needs to change. Have a read here: http://fideloper.com/laravel-multiple-database-connections.
There's a Eloquent method on() for specifying the connection, here's an example:
The Eloquent example looks like what you need:
$results = Model::on('mysql')->find(1);
Add both connections to your database config and then change the on() part depending on which DB you need to query.
Update: misunderstood the question
If you only need to change the table and not the database, you can use setTable()
$model = new Model
$model->setTable('b');
$model->find(1);
Although that may get confusing.
Instead, you could also define a base model and then extend it with the only difference being the table protected $table = 'b';
You can always change the default database for the one you need. I use this Config::set('database.default', 'chronos');, where chronos is one of my databases. When I need to change to the "other", I just change de database name. You can call it wherever you want. I think that what you're looking for is just switch between the databases.
You need to have two different models, one for each table on each database, though.
Let me know if I got it wrong.

How to self-initialize Doctrine_Record (as if Doctrine_Query would)?

I have models that extend Doctrine_Record, and are directly mapped to one specific record from the database (that is, one record with given a given id, hardcoded statically in the class).
Now, I want the specific record class to initialize itself as if Doctrine_Query would. So, this would be the normal procedure:
$query = new Doctrine_Query();
$model = $query->from('Model o')->where('id = ?', 123)->fetchOne();
I would like to do something like this
$model = new Model();
And in the Model:
const ID = 123;
//note that __construct() is used by Doctrine_Record so we need construct() without the __
public function construct()
{
$this->id = self::ID;
//what here??
$this->initialize('?????');
}
So for clarity's sake: I would like the object to be exactly the same as if it would be received from a query (same state, same attributes and relations and such).
Any help would be greatly appreciated: thank you.
The first thing I need to say is I'd put the constant in the class. So like this:
class Application_Model_Person
{
const ID = 1234;
}
Then, a Doctrine method like Doctrine_Record::fetchOne() is always returning a (new) instance of the model and never merges the data with the record you're calling fetchOne() to. Doctrine is nevertheless able to merge a retreived record with another class, so it rather simple to do:
class Application_Model_Person extends Doctrine_Record_Abstract
{
const ID = 1234;
public function __construct($table = null, $isNewEntry = false)
{
// Calling Doctrine_Record::__construct
parent::__construct($table, $isNewEntry);
// Fetch the record from database with the id self::ID
$record = $this->getTable()->fetchOne(self::ID);
$this->merge($record);
}
}
Then you're able to do:
$model = new Application_Model_Person;
echo $model->id; // 1234
Although having multiple classes for the same data type (i.e. table) is really not what ORM should be like, what you want can be done in Doctrine using Column aggregation inheritance. Assuming you are using Doctrine 1.2.x, you can write the following YML:
Vehicle:
columns:
brand: string(100)
fuelType: string(100)
Car:
inheritance:
extends: Entity
type: column_aggregation
keyField: type
keyValue: 1
Bicycle:
inheritance:
extends: Entity
type: column_aggregation
keyField: type
keyValue: 2
Now, the Vehicle table will have a 'type' column, that determines the class that Doctrine will instantiate when you select a vehicle. You will have three classes: Vehicle, Car and Bicycle. You can give every class their own methods etc, while the records their instances represent reside in the same database table. If you use $a = new Bicycle, Doctrine automatically sets the type for you so you don't have to take care of that.
I don't think a model instance could decide to hang on a certain database entry after it has been initialized. That said, you can do something like this:
<?php
class Model extends baseModel {
public static function create($id = null)
{
if ($id === null) return new Model;
return Doctrine::getTable('Model')->findeOneById($id);
}
}
And then, you can either use
$newModel = Model::create();
Or fetch an existing one having ID 14 (for example) using
$newModel = Model::create(14);
Or, if you want your 123 to be default instead of a new item, declare the function like this:
public static function create($id = 123)

Categories