Extending Collection in Laravel - php

I'm trying to extend my model collections in Laravel. I'm following these two tutorials here that say I just need to override the newCollection method. Seems pretty straight forward enough, but it seems the newCollection method is not firing.
I'm using Laravel 4.2.
link1
link2
My Model:
class City extends Eloquent {
public function newCollection(array $models = Array())
{
echo 'here';
return new Extensions\CityCollection($models);
}
}
Custom Collection:
<?php namespace Extensions;
echo 'here';
class CityCollection extends \Illuminate\Database\Eloquent\Collection {
public function findInList($name)
{
foreach ($this as $city)
{
if (strtolower($city->name) === strtolower($name))
{
return $city;
}
}
return false;
}
}
None of the echo statements are firing. Am I doing something wrong? I've also run a dump-autoload and have added the containing folder to my composer.json.

I was about to delete, but maybe this will help someone in the future. It turns out I just needed to flush my models cache.

Related

laravel observers are not working

I am trying to listen to model events using laravel observers .The problem is when i submit my form (update or creating new records), nothing happened at all .Do i miss something ?
app.php
'providers' => [
...
App\Providers\CasesManagerServiceProvider::class,
]
CasesManagerServiceProvider.php
class CasesManagerServiceProvider extends ServiceProvider
{
public function boot( )
{
Cases::observe(CasesObserver::class);
}
public function register()
{
}
}
CasesObserver.php
class CasesObserver
{
private $cases;
public function __construct(Cases $cases){
$this->cases = $cases;
}
public function creating(Cases $case)
{
dd('creating');
}
public function saved(Cases $case)
{
dd('saved');
}
public function updating($case)
{
dd('updating');
}
public function updated($case)
{
dd('updated');
}
}
Cases.php
class Cases extends Model
{
const UPDATED_AT = 'modified_at';
protected $dispatchesEvents = [
'updating' => CasesObserver::class,
'updated' => CasesObserver::class,
'creating' => CasesObserver::class,
'saved' => CasesObserver::class,
];
}
for me, the problem was registering observer in the register() method!
so when I put it in the boot() method every thing worked well! the reason is the order of running methods in service providers which are mentioned hear
hope be useful
Ok i have found my answer . All the problem was when I added
use app\Observers\CasesObserver; in CasesManagerServiceProvider.php instead of use App\Observers\CasesObserver; .
Yes the Camel case of App was the problem, so i changed to App and all things are working fine now.
It seems to be a misuse of Composer and Laravel themselves.
You should inform them that you have added some files and configurations:
To autoload the files:
composer dump
To reconfigure the cache:
php artisan config:cache
Hope this help you too!
You do not need to use $dispatchesEvents in your case. You should try to remove $dispatchesEvents from model, and remove __constructor() from CasesObserver.
The reason is that you have to add a HasEvents trait to your model
<?php
use Illuminate\Database\Eloquent\Concerns\HasEvents;
class MyModel extends Model
{
use HasEvents;
//your code goes here
}
Not possible according to the documentation. When issuing a mass update or delete query via Eloquent.

Laravel: One Controller for multiple Models

I'm currently rebuilding my vanilla-PHP-App with Laravel and I have the following problem.
I have multiple database-tables, that represent word categories (noun, verb, adverb, ...). For each table I created a separate Model, a route::resource and a separate resource-Controller. For example:
NomenController.php
public function show($id)
{
$vocab = Nomen::find($id);
return view('glossarium.vocab_update', compact('vocab'));
}
and
VerbController.php
public function show($id)
{
$vocab = Verb::find($id);
return view('glossarium.vocab_update', compact('vocab'));
}
...which are essentially the same except the Model class.
I don't want to create a separate Controller for each model, that does exactly the same. What would be the most simple and elegant way to solve this?
Should I just create a VocabController.php and add a parameter for the Model-name like:
Route::resource('/vocab/{category}', 'VocabController');
and then add a constructor method in this controller like
public function __construct ($category) {
if ($category == 'nomen') {
$this->vocab = App\Nomen;
}
else if ($category == 'verb') {
$this->vocab = App\Verb;
}
}
I wonder if there is a simpler method to do that. Can I somehow do this with Route Model Binding?
Thanks in advance
Simply create a trait like this in App\Traits, (you can name it anything... Don't go with mine though... I feel its pretty lame... :P)
namespace App\Traits;
trait CommonControllerFunctions {
public function show($id) {
$modelObject = $this->model;
$model = $modelObject::find($id);
return view('glossarium.vocab_update', compact('model'));
}
}
and in your NomenController and VerbController, do this:
use App\Traits\CommonControllerFunctions;
class NomenController {
use CommonControllerFunctions;
protected $model = Nomen::class;
}
and
use App\Traits\CommonControllerFunctions;
class VerbController {
use CommonControllerFunctions;
protected $model = Verb::class;
}
Note: Please note that this example is just a work-around for your particular situation only... Everyone practices code differently, so this method might not be approved by all...
I think the simpliest way it to create only one controller, eg VocabController with methods nomen, verb and whatever you want.
Routes:
Route::get('/vocab/nomen/{nomen}', 'VocabController#item');
Route::get('/vocab/verb/{verb}', 'VocabController#item');
And the model binding:
Route::model('nomen', 'App\Nomen');
Route::model('verb', 'App\Varb');
Then your method shoud look like that:
public function item($item)
{
return view('glossarium.vocab_update', $item);
}
Keep in mind, that $item is already fetched model from the database.

Laravel retrieving eloquent nested eager loading

In my application i have 4 models that relate to each other.
Forms->categories->fields->triggers
What I am trying to do is get the Triggers that refer to the current Form.
Upon researching i found nested eager loading, which would require my code to look like this
Form::with('categories.fields.triggers')->get();
Looking through the response of this i can clearly see the relations all the way down to my desired triggers.
Now the part I'm struggling with is only getting the triggers, without looping through each model.
The code i know works:
$form = Form::findOrFail($id);
$categories = $form->categories;
foreach ($categories as $category) {
$fields = $category->fields;
foreach ($fields as $field) {
$triggers[] = $field->triggers;
}
}
I know this works, but can it be simplified? Is it possible to write:
$form = Form::with('categories.fields.triggers')->get()
$triggers = $form->categories->fields->triggers;
To get the triggers related? Doing this as of right now results in:
Undefined property: Illuminate\Database\Eloquent\Collection::$categories
Since it is trying to run the $form->categories on a collection.
How would i go about doing this? Do i need to use the HasManyThrough relation on my models?
My models
class Form extends Model
{
public function categories()
{
return $this->hasMany('App\Category');
}
}
class Category extends Model
{
public function form()
{
return $this->belongsTo('App\Form');
}
public function fields()
{
return $this->hasMany('App\Field');
}
}
class Field extends Model
{
public function category()
{
return $this->belongsTo('App\Category');
}
public function triggers()
{
return $this->belongsToMany('App\Trigger');
}
}
class Trigger extends Model
{
public function fields()
{
return $this->belongsToMany('App\Field');
}
}
The triggers run through a pivot table, but should be reachable with the same method?
I created a HasManyThrough relationship with unlimited levels and support for BelongsToMany:
Repository on GitHub
After the installation, you can use it like this:
class Form extends Model {
use \Staudenmeir\EloquentHasManyDeep\HasRelationships;
public function triggers() {
return $this->hasManyDeep(Trigger::class, [Category::class, Field::class, 'field_trigger']);
}
}
Form::with('triggers')->get();
Form::findOrFail($id)->triggers;

Traits with PHP and Laravel

I am using Laravel 5.1 and would like to access an array on the Model from the Trait when the Model before the model uses the appends array.
I would like to add certain items to the appends array if it exists from my trait. I don't want to edit the model in order to achieve this. Are traits actually usable in this scenario or should I use inheritance?
array_push($this->appends, 'saucedByCurrentUser');
Here is how my current setup works.
Trait
<?php namespace App;
trait AwesomeSauceTrait {
/**
* Collection of the sauce on this record
*/
public function awesomeSauced()
{
return $this->morphMany('App\AwesomeSauce', 'sauceable')->latest();
}
public function getSaucedByCurrentUserAttribute()
{
if(\Auth::guest()){
return false;
}
$i = $this->awesomeSauced()->whereUserId(\Auth::user()->id)->count();
if ($i > 0){
return true;
}
return false;
}
}
Model
<?php namespace App;
use App\AwesomeSauceTrait;
use Illuminate\Database\Eloquent\Model;
class FairlyBlandModel extends Model {
use AwesomeSauceTrait;
protected $appends = array('age','saucedByCurrentUser');
}
What I would like to do is something to achieve the same effect as extending a class. I have a few similar traits, so using inheritance gets somewhat ugly.
trait AwesomeSauceTrait {
function __construct() {
parent::__construct();
array_push($this->appends, 'saucedByCurrentUser');
}
}
I have seen some workarounds for this, but none of them seem better/cleaner than just adding the item to the array manually. Any ideas are appreciated.
Update
I discovered this way of accomplishing what I need for one trait, but it only works for one trait and I don't see an advantage of using this over inheritance.
trait
protected $awesomeSauceAppends = ['sauced_by_current_user'];
protected function getArrayableAppends()
{
array_merge($this->appends, $this->awesomeSauceAppends);
parent::getArrayableAppends();
}
How I am currently handling my Model, for what it is worth.
model
public function __construct()
{
array_merge($this->appends, $this->awesomeSauceAppends);
}
Traits are sometimes described as "compiler-assisted copy-and-paste"; the result of using a Trait can always be written out as a valid class in its own right. There is therefore no notion of parent in a Trait, because once the Trait has been applied, its methods are indistinguishable from those defined in the class itself, or imported from other Traits at the same time.
Similarly, as the PHP docs say:
If two Traits insert a method with the same name, a fatal error is produced, if the conflict is not explicitly resolved.
As such, they are not very suitable for situations where you want to mix in multiple variants of the same piece of behaviour, because there is no way for base functionality and mixed in functionality to talk to each other in a generic way.
In my understanding the problem you're actually trying to solve is this:
add custom Accessors and Mutators to an Eloquent model class
add additional items to the protected $appends array matching these methods
One approach would be to continue to use Traits, and use Reflection to dynamically discover which methods have been added. However, beware that Reflection has a reputation for being rather slow.
To do this, we first implement a constructor with a loop which we can hook into just by naming a method in a particular way. This can be placed into a Trait of its own (alternatively, you could sub-class the Eloquent Model class with your own enhanced version):
trait AppendingGlue {
public function __construct() {
// parent refers not to the class being mixed into, but its parent
parent::__construct();
// Find and execute all methods beginning 'extraConstruct'
$mirror = new ReflectionClass($this);
foreach ( $mirror->getMethods() as $method ) {
if ( strpos($method->getName(), 'extraConstruct') === 0 ) {
$method->invoke($this);
}
}
}
}
Then any number of Traits implementing differently named extraConstruct methods:
trait AwesomeSauce {
public function extraConstructAwesomeSauce() {
$this->appends[] = 'awesome_sauce';
}
public function doAwesomeSauceStuff() {
}
}
trait ChocolateSprinkles {
public function extraConstructChocolateSprinkles() {
$this->appends[] = 'chocolate_sprinkles';
}
public function doChocolateSprinklesStuff() {
}
}
Finally, we mix in all the traits into a plain model, and check the result:
class BaseModel {
protected $appends = array('base');
public function __construct() {
echo "Base constructor run OK.\n";
}
public function getAppends() {
return $this->appends;
}
}
class DecoratedModel extends BaseModel {
use AppendingGlue, AwesomeSauce, ChocolateSprinkles;
}
$dm = new DecoratedModel;
print_r($dm->getAppends());
We can set the initial content of $appends inside the decorated model itself, and it will replace the BaseModel definition, but not interrupt the other Traits:
class ReDecoratedModel extends BaseModel {
use AppendingGlue, AwesomeSauce, ChocolateSprinkles;
protected $appends = ['switched_base'];
}
However, if you over-ride the constructor at the same time as mixing in the AppendingGlue, you do need to do a bit of extra work, as discussed in this previous answer. It's similar to calling parent::__construct in an inheritance situation, but you have to alias the trait's constructor in order to access it:
class ReConstructedModel extends BaseModel {
use AppendingGlue { __construct as private appendingGlueConstructor; }
use AwesomeSauce, ChocolateSprinkles;
public function __construct() {
// Call the mixed-in constructor explicitly, like you would the parent
// Note that it will call the real parent as well, as though it was a grand-parent
$this->appendingGlueConstructor();
echo "New constructor executed!\n";
}
}
This can be avoided by inheriting from a class which either exists instead of the AppendingGlue trait, or already uses it:
class GluedModel extends BaseModel {
use AppendingGlue;
}
class ReConstructedGluedModel extends GluedModel {
use AwesomeSauce, ChocolateSprinkles;
public function __construct() {
// Standard call to the parent constructor
parent::__construct();
echo "New constructor executed!\n";
}
}
Here's a live demo of all of that put together.
I thought I'd add an update for 2019 since this was one of the first discussions that popped up when trying to do a similar thing. I'm using Laravel 5.7 and nowadays Laravel will do the reflection that IMSoP mentioned.
After the trait has been booted, Laravel will then call initializeTraitName() on the constructed object (where TraitName is the full name of the trait).
To add extra items to $appends from a trait, you could simply do this...
trait AwesomeSauceTrait {
public function initializeAwesomeSauceTrait()
{
$this->appends[] = 'sauced_by_current_user';
}
public function getSaucedByCurrentUserAttribute()
{
return 'whatever';
}
}
KISS:
I don't see any reason why you should use trait when your are simply appending attributes.
I would only recommend using trait without a constructor like you were doing, only if you model is getting pretty bulky and you wish to slim down things.
Please also note this not the correct way of appending attribute
protected $appends = array('age','saucedByCurrentUser');
You could do this:
protected $appends = array('age','sauced_by_current_user');
Appends attribute names should the snake_case of its method Name
Edited:
The idea behind appends is to dynamically add fields that doesn't exist in your database table to your model so after you can do like:
$model = FairlyBlandModel ::find(1);
dd($model->sauced_by_current_user);

Laravel class not found with one-to-many

I'm trying to return an object Contract and all of it's related Project. I can return all of the Contracts but when I try to get the contract's Project, I get a "Class 'EstimateProject' not found" error. I've run composer dump-autoload to reload the class mappings, but I still get the error. Any ideas? Here's my class setup:
EDIT: Just wanted to add that LaravelBook\Ardent\Ardent\ is an extension of Laravel's Model.php. It adds validation to model on the Save function. I've made Ardent extend another plugin I've added that is a MongoDB version of the Eloquent ORM.
EstimateContract.php
<?php namespace Test\Tools;
use LaravelBook\Ardent\Ardent;
class EstimateContract extends Ardent {
// This sets the value on the Mongodb plugin's '$collection'
protected $collection = 'Contracts';
public function projects()
{
return $this->hasMany('EstimateProject', 'contractId');
}
}
EstimateProject.php
<?php namespace Test\Tools;
use LaravelBook\Ardent\Ardent;
class EstimateProject extends Ardent {
// This sets the value on the Mongodb plugin's '$collection'
protected $collection = 'Projects';
public function contract()
{
return $this->belongsTo('EstimateContract', 'contractId');
}
}
EstimateContractController.php
<?php
use \Test\Tools\EstimateContract;
class EstimateContractsController extends \BaseController {
/**
* Display a listing of the resource.
*
* #return Response
*/
public function index()
{
$contracts = EstimateContract::all();
echo $contracts;
foreach($contracts as $contract)
{
if($contract->projects)
{
echo $contract->projects;
}
}
}
}
In order for this to work, I needed to fully qualify the EstimateProject string in my EstimateContract model.
The solution was to change it from:
return $this->hasMany('EstimateProject', 'contractId');
to
return $this->hasMany('\Test\Tools\EstimateProject', 'contractId');
You have to use the fully qualified name, but I got the same error when I used forward slashes instead of back slashes:
//Correct
return $this->hasMany('Fully\Qualified\ClassName');
//Incorrect
return $this->hasMany('Fully/Qualified/ClassName');

Categories