Implementing Interface on Model in Laravel - laravel-5

I have a Laravel 5 model Account which implements an Interface.
I have implemented all the methods of the Interface however when I run the code, Laravel complains that the Model does not implement the interface.
Error below
Account.php (Model)
<?php
namespace Abc\Accounts\Models;
use Abc\Accounts\Contracts\Accountlnterface;
use Illuminate\Database\Eloquent\Model;
class Account extends Model implements Accountlnterface {
....
In my Controller I am doing this
$account = Account::where('something', 'value')->first();
This returns the model just fine.
The problem lies when I pass it into another class controller
$result = new Transaction($account, 5.00);
Transaction file
public function __construct(Accountlnterface $account, float $value = 0.00)
{
$this->account = $account;
The transaction constructor is looking for the Interface however laravel is complaining that Account does not implement it.
Im not sure whats wrong with this code.
Error from Laravel
Type error: Argument 1 passed to
Abc....\Transaction::__construct() must be an instance of
Abc\Accounts\Components\Accountlnterface, instance of
Tymr\Plugins\Accounts\Models\Account given.
Directly after the model is loaded I ran this code
if($account instanceof AccountInterface)
echo "working";
else
echo "fails";
and it certainly fails.

You need to register a service container binding in one of your service providers or better create a new service provider.
It helps Laravel know the implementation to use for your interface.
use Illuminate\Support\ServiceProvider;
class ModelsServiceProvider extends ServiceProvider {
public function register()
{
$this->app->bind(
'Abc\Accounts\Contracts\Accountlnterface',
'Abc\Accounts\Models\Account'
);
}
}
In app/config/app.php, register your service provider under the available providers.
'providers' => [
...
'ModelsServiceProvider',
]

Related

How to mock laravel model relathipship?

I have a model that has a relationship with a View, that is complicate to popolate for make the feature test, but in the same time this is called from some component that are inside the controller called.
The following code is an example:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Models\TemperatureView;
class Town extends Model
{
function temperature()
{
return $this->hasOne(TemperatureView::class);
}
}
This is an example of the controller:
<?php
namespace App\Http\Controllers;
use App\Models\Town;
class TownController extends Controller
{
public function update($id)
{
// Here is the validation and update of Town model
$UpdatedTown = Town::where('id',$id);
$UpdatedTown->update($data);
$this->someOperation($UpdatedTown);
}
private function someOperation($Town)
{
//Here there is some operation that use the temperature Relationship
/*
Example:
$Town->temperature->value;
*/
}
}
The test is like is like this:
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Tests\TestCase;
use App\Models\TownModel;
use Mockery;
use Mockery\MockInterface;
class TownTest extends TestCase
{
/**
* A basic test example.
*
* #return void
*/
public function test_get_town_temperature()
{
$payload = ['someTownInformation' => 'Value'];
$response = $this->post('/Town/'.$idTown,$payload);
$response->assertStatus(200);
//This test failed
}
public function test_get_town_temperature_with_mocking()
{
$this->instance(
TownModel::class,
Mockery::mock(TownModel::class, function (MockInterface $mock) {
$MockDataTemperature = (object) array('value'=>2);
$mock->shouldReceive('temperature')->andReturn($MockDataTemperature);
})
);
$payload = ['someTownInformation' => 'Value'];
$response = $this->post('/Town/'.$idTown,$payload);
$response->assertStatus(200);
//This test also failed
}
}
The first test failed because the Controller has some check on the relationship temperature, that is empty because the view on database is empty.
The second test failed also for the same reason. I tried to follow some others questions with the official guide of Laravel Mocking. I know this is mocking object and not specially Eloquent.
Is something I'm not setting well?
If it's not possible to mock only the function, is possible to mock all the relationship of view, bypassing the DB access to that?
Edit
I undestand that the mocking work only when the class is injected from laravel, so what I wrote above it's not pratical.
I don't know if it's possible to mock only it, I saw a different option, that to create the interface of the model and change for the test, but I didn't want to make it.

Non-static method Spatie\Analytics\Analytics::fetchMostVisitedPages() should not be called statically in laravel 8

I'm using laravel 8 and I'm trying to use spatie\laravel-analytics, but I'm getting this error
Non-static method Spatie\Analytics\Analytics::fetchMostVisitedPages() should not be called statically
I've tried what people have suggested, but I don't know if I'm missing something. So I'm hoping someone can check it out and let me know.
Here is my code
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Http;
use Spatie\Analytics\Analytics as Analytics;
use Spatie\Analytics\Period;
class GoogleReportController extends Controller
{
public function index()
{
$test = Analytics::fetchMostVisitedpages(Period::days(7));
dd($test);
}
}
As you can see in the source the class methods of Analytics are not static so you cannot call them statically. It's intended to be used as a singleton (and there's good reasons for that). Laravel offers Facades as "wrappers" to singleton classes to allow a static-like access and in the case of this library you can utilise one as follows:
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Http;
use Spatie\Analytics\AnalyticsFacade as Analytics; //Change here
use Spatie\Analytics\Period;
class GoogleReportController extends Controller
{
public function index()
{
$test = Analytics::fetchMostVisitedpages(Period::days(7));
dd($test);
}
}

How do I over ride (new class) a facade in Laravel 5.4?

I've get the following class
<?php
namespace App;
class Currency extends \Casinelli\Currency\Currency
{
/**
* Create a new instance.
*
* #param \Illuminate\Foundation\Application $app
*/
public function __construct($app)
{
parent::__construct($app);
$this->setCurrency(getCurrency());
}
}
I've replaced the 'aliases' in app.php:
- 'Currency' => \Casinelli\Currency\Facades\Currency::class,
+ 'Currency' => \App\Currency::class,
However, I'm running into an error of:
Non-static method Casinelli\Currency\Currency::rounded() should not be called statically
It seems my Currency class is not being treated as a Facade... How would I go about resolving this?
Before answering, I recommend to fork the package, do your modifications and use your fork. Because if something change in the package, your override may not work anymore.
Let's take a look at the package.
You have 3 important files :
The currency that you want to extend:
https://github.com/Casinelli/Currency/blob/master/src/Casinelli/Currency/Currency.php
The facade that you want to use :
https://github.com/Casinelli/Currency/blob/master/src/Casinelli/Currency/Facades/Currency.php
And finally, the ServiceProvider that register the class you want to extends:
https://github.com/Casinelli/Currency/blob/master/src/Casinelli/Currency/CurrencyServiceProvider.php#L60
The service provider will register the class Currency as a singleton with the alias of currency
Then, when you call the facade Currency, it will look for the alias currency and return a the instance of the class Currency.
Implement your own Currency
To use your own Currency class, you will need to register your own implementation of the Currency class in a service provider that will replace the service provider of the package.
Create your own serviceProvider
$ php artisan make:provider ExtendedCurrencyServiceProvider
In your file app/config/app.php,
Replace Casinelli\Currency\CurrencyServiceProvider::class,
with App\Providers\ExtendedCurrencyServiceProvider::class,
In your new service provider change to this
<?php
namespace App\Providers;
use Casinelli\Currency\CurrencyServiceProvider;
class ExtendedCurrencyServiceProvider extends CurrencyServiceProvider
{
/**
* Register currency provider.
*/
public function registerCurrency()
{
$this->app->singleton('currency', function ($app) {
return new App\Yournamespace\CurrencyClass($app);
});
}
}
Laravel 5.5+ In your composer.json remove the service provider from the autodiscovery
"extra": {
"laravel": {
"dont-discover": [
"Casinelli\\Currency\\CurrencyServiceProvider"
]
}
},
Now, when you will call \Currency::rounded() it will call your own implementation of the currency.
You don't need to change the Facade.
The error message / exception is very misleading...
The issue was due to calling App\Currency::rounded instead of: Casinelli\Currency\Facades\Currency::rounded...

laravel model relation not working

I have created a laravel api for my application.I have used Pingpong module package for different modules.I am having hard time establishing many-to-many relation.I have 3 tables:roles,groups,group_roles.And my models are:
Group.php
namespace Modules\User\Entities;
use Illuminate\Database\Eloquent\Model;
class Group extends Model {
protected $fillable = [];
protected $table='groups';
public static function roles(){
return $this->belongsToMany('Modules\User\Entities\Role','group_roles','group_id','role_id');
}
}
Role.php
namespace Modules\User\Entities;
use Illuminate\Database\Eloquent\Model;
class Role extends Model {
protected $fillable = [];
protected $table='roles';
public function groups(){
return $this->belongsToMany('Modules\User\Entities\Group','group_roles','group_id','role_id');
}
}
And my controller
namespace Modules\User\Http\Controllers;
use Pingpong\Modules\Routing\Controller;
use Modules\User\Entities\Group;
use Modules\User\Entities\Role;
use Illuminate\Http\Request;
use App\Login;
use Input;
use Validator;
use Hash;
use Response;
class UserController extends Controller {
public function getGroupById(Request $request){
$groups=Group::with('roles')->get();
return Response::json ([
'status'=>'ok',
'group'=>$groups
],200);
}
}
The problem is I am not able to establish the relation between the models and the getGroupById returns 500 internal error response.$group=Group::all(); $group=Group::find($request['id']); returns fine but it is not returning related roles.
Similar structure and codes work fine on app without the use pingpong.
Your relationships are currently like this:
// not sure why this is static?
public static function roles(){
return $this->belongsToMany('Modules\User\Entities\Role', 'group_roles', 'group_id', 'role_id');
}
public function groups(){
return $this->belongsToMany('Modules\User\Entities\Group', 'group_roles', 'group_id', 'role_id');
}
Please note from the docs, regarding the belongsToMany method:
The third argument is the foreign key name of the model on which you are defining the relationship, while the fourth argument is the foreign key name of the model that you are joining to...
So with this in mind I think your relationships may be incorrect due to using the wrong arguments on your belongsToMany method calls. I think it should be like this:
public function roles(){
return $this->belongsToMany('Modules\User\Entities\Role', 'group_roles', 'group_id', 'role_id');
}
public function groups(){
return $this->belongsToMany('Modules\User\Entities\Group', 'group_roles', 'role_id', 'group_id');
}
Also if you have intermediate table columns you'd need to declare those on the belongsToMany call.
Hope that helps!
Edit
Firstly, you said getGroupById returns 500 internal error response. Have you tried checking what the actual error is!? 500 internal error doesn't provide much info, I'm sure you'd get to the bottom of things a lot faster if you found out the exact issue through laravel's usual error response page.
I assume you're doing this through an ajax request so you could use the network tab if you're using chrome then click on the 500 request to see the error laravel returns or you can use something like postman and hit the url through that.
If I wanted to quickly check the functionality of the models relationship methods, I'd do the following:
After setting up some data for a group and relationship, could you try running this in tinker or a route for testing/debugging.
$g = Group::first(); // get the first group, or you could use find($id) if you had a specific group in mind
// if you're in tinker
$g->roles; // show the roles
// if you're running on a route
dd($g->roles); // show the roles
While haakym's answer is very detailed, but you can also try changing your mapping table name to convention based 'group_role' instead of 'group_roles'. With this method you will have to supply only one argument to belongsToMany call.
Note that in general it should not matter if the other arguments are correct, but its just another step to debug!

global variable for all controller and views

In Laravel I have a table settings and i have fetched complete data from the table in the BaseController, as following
public function __construct()
{
// Fetch the Site Settings object
$site_settings = Setting::all();
View::share('site_settings', $site_settings);
}
Now i want to access $site_settings. in all other controllers and views so that i don't need to write the same code again and again, so anybody please tell me the solution or any other way so i can fetch the data from the table once and use it in all controllers and view.
Okay, I'm going to completely ignore the ridiculous amount of over engineering and assumptions that the other answers are rife with, and go with the simple option.
If you're okay for there to be a single database call during each request, then the method is simple, alarmingly so:
class BaseController extends \Controller
{
protected $site_settings;
public function __construct()
{
// Fetch the Site Settings object
$this->site_settings = Setting::all();
View::share('site_settings', $this->site_settings);
}
}
Now providing that all of your controllers extend this BaseController, they can just do $this->site_settings.
If you wish to limit the amount of queries across multiple requests, you could use a caching solution as previously provided, but based on your question, the simple answer is a class property.
At first, a config file is appropriate for this kind of things but you may also use another approach, which is as given below (Laravel - 4):
// You can keep this in your filters.php file
App::before(function($request) {
App::singleton('site_settings', function(){
return Setting::all();
});
// If you use this line of code then it'll be available in any view
// as $site_settings but you may also use app('site_settings') as well
View::share('site_settings', app('site_settings'));
});
To get the same data in any controller you may use:
$site_settings = app('site_settings');
There are many ways, just use one or another, which one you prefer but I'm using the Container.
Use the Config class:
Config::set('site_settings', $site_settings);
Config::get('site_settings');
http://laravel.com/docs/4.2/configuration
Configuration values that are set at run-time are only set for the current request, and will not be carried over to subsequent requests.
In Laravel, 5+ you can create a file in the config folder and create variables in that and use that across the app.
For instance, I want to store some information based on the site.
I create a file called site_vars.php,
which looks like this
<?php
return [
'supportEmail' => 'email#gmail.com',
'adminEmail' => 'admin#sitename.com'
];
Now in the routes, controller, views you can access it using
Config::get('site_vars.supportEmail')
In the views if I this
{{ Config::get('site_vars.supportEmail') }}
It will give email#gmail.com
Hope this helps.
EDiT-
You can also define vars in .env file and use them here.
That is the best way in my opinion as it gives you the flexibility to use values that you want on your local machine.
So, you can do something this in the array
'supportEmail' => env('SUPPORT_EMAIL', 'defaultmail#gmail.com')
Important - After you do this, don't forget to do this on production env
php artisan config:cache
In case, there's still some problem, then you can do this (usually it would never happen but still if it ever happens)
php artisan cache:clear
php artisan config:cache
In your local env, always do this after this adding it
php artisan config:clear
It's always a good practice not to cache config vars in local. in case, it was cached, this would remove the cache and would load the new changes.
I see, that this is still needed for 5.4+ and I just had the same problem, but none of the answers were clean enough, so I tried to accomplish the availability with ServiceProviders. Here is what i did:
Created the Provider SettingsServiceProvider
php artisan make:provider SettingsServiceProvider
Created the Model i needed (GlobalSettings)
php artisan make:model GlobalSettings
Edited the generated register method in \App\Providers\SettingsServiceProvider. As you can see, I retrieve my settings using the eloquent model for it with Setting::all().
public function register()
{
$this->app->singleton('App\GlobalSettings', function ($app) {
return new GlobalSettings(Setting::all());
});
}
Defined some useful parameters and methods (including the constructor with the needed Collection parameter) in GlobalSettings
class GlobalSettings extends Model
{
protected $settings;
protected $keyValuePair;
public function __construct(Collection $settings)
{
$this->settings = $settings;
foreach ($settings as $setting){
$this->keyValuePair[$setting->key] = $setting->value;
}
}
public function has(string $key){ /* check key exists */ }
public function contains(string $key){ /* check value exists */ }
public function get(string $key){ /* get by key */ }
}
At last I registered the provider in config/app.php
'providers' => [
// [...]
App\Providers\SettingsServiceProvider::class
]
After clearing the config cache with php artisan config:cache you can use your singleton as follows.
$foo = app(App\GlobalSettings::class);
echo $foo->has("company") ? $foo->get("company") : "Stack Exchange Inc.";
You can read more about service containers and service providers in Laravel Docs > Service Container and Laravel Docs > Service Providers.
This is my first answer and I had not much time to write it down, so the formatting ist a bit spacey, but I hope you get everything.
I forgot to include the boot method of SettingsServiceProvider, to make the settings variable global available in views, so here you go:
public function boot(GlobalSettings $settinsInstance)
{
View::share('globalsettings', $settinsInstance);
}
Before the boot methods are called all providers have been registered, so we can just use our GlobalSettings instance as parameter, so it can be injected by Laravel.
In blade template:
{{ $globalsettings->get("company") }}
View::share('site_settings', $site_settings);
Add to
app->Providers->AppServiceProvider file boot method
it's global variable.
Most popular answers here with BaseController didn't worked for me on Laravel 5.4, but they have worked on 5.3. No idea why.
I have found a way which works on Laravel 5.4 and gives variables even for views which are skipping controllers. And, of course, you can get variables from the database.
add in your app/Providers/AppServiceProvider.php
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
// Using view composer to set following variables globally
view()->composer('*',function($view) {
$view->with('user', Auth::user());
$view->with('social', Social::all());
// if you need to access in controller and views:
Config::set('something', $something);
});
}
}
credit: http://laraveldaily.com/global-variables-in-base-controller/
In Laravel 5+, to set a variable just once and access it 'globally', I find it easiest to just add it as an attribute to the Request:
$request->attributes->add(['myVar' => $myVar]);
Then you can access it from any of your controllers using:
$myVar = $request->get('myVar');
and from any of your blades using:
{{ Request::get('myVar') }}
In Laravel 5.1 I needed a global variable populated with model data accessible in all views.
I followed a similar approach to ollieread's answer and was able to use my variable ($notifications) in any view.
My controller location: /app/Http/Controllers/Controller.php
<?php
namespace App\Http\Controllers;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use App\Models\Main as MainModel;
use View;
abstract class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
public function __construct() {
$oMainM = new MainModel;
$notifications = $oMainM->get_notifications();
View::share('notifications', $notifications);
}
}
My model location: /app/Models/Main.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use DB;
class Main extends Model
{
public function get_notifications() {...
I have found a better way which works on Laravel 5.5 and makes variables accessible by views. And you can retrieve data from the database, do your logic by importing your Model just as you would in your controller.
The "*" means you are referencing all views, if you research more you can choose views to affect.
add in your app/Providers/AppServiceProvider.php
<?php
namespace App\Providers;
use Illuminate\Contracts\View\View;
use Illuminate\Support\ServiceProvider;
use App\Setting;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
// Fetch the Site Settings object
view()->composer('*', function(View $view) {
$site_settings = Setting::all();
$view->with('site_settings', $site_settings);
});
}
/**
* Register any application services.
*
* #return void
*/
public function register()
{
}
}
If you are worried about repeated database access, make sure that you have some kind of caching built into your method so that database calls are only made once per page request.
Something like (simplified example):
class Settings {
static protected $all;
static public function cachedAll() {
if (empty(self::$all)) {
self::$all = self::all();
}
return self::$all;
}
}
Then you would access Settings::cachedAll() instead of all() and this would only make one database call per page request. Subsequent calls will use the already-retrieved contents cached in the class variable.
The above example is super simple, and uses an in-memory cache so it only lasts for the single request. If you wanted to, you could use Laravel's caching (using Redis or Memcached) to persist your settings across multiple requests. You can read more about the very simple caching options here:
http://laravel.com/docs/cache
For example you could add a method to your Settings model that looks like:
static public function getSettings() {
$settings = Cache::remember('settings', 60, function() {
return Settings::all();
});
return $settings;
}
This would only make a database call every 60 minutes otherwise it would return the cached value whenever you call Settings::getSettings().
You can also use Laravel helper which I'm using.
Just create Helpers folder under App folder
then add the following code:
namespace App\Helpers;
Use SettingModel;
class SiteHelper
{
public static function settings()
{
if(null !== session('settings')){
$settings = session('settings');
}else{
$settings = SettingModel::all();
session(['settings' => $settings]);
}
return $settings;
}
}
then add it on you config > app.php under alliases
'aliases' => [
....
'Site' => App\Helpers\SiteHelper::class,
]
1. To Use in Controller
use Site;
class SettingsController extends Controller
{
public function index()
{
$settings = Site::settings();
return $settings;
}
}
2. To Use in View:
Site::settings()
A global variable for using in controllers; you can set in AppServiceProvider like this :
public function boot()
{
$company=DB::table('company')->where('id',1)->first();
config(['yourconfig.company' => $company]);
}
usage
config('yourconfig.company');
using middlwares
1- create middlware with any name
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\View;
class GlobalData
{
public function handle($request, Closure $next)
{
// edit this section and share what do you want
$site_settings = Setting::all();
View::share('site_settings', $site_settings);
return $next($request);
}
}
2- register your middleware in Kernal.php
protected $routeMiddleware = [
.
...
'globaldata' => GlobalData::class,
]
3-now group your routes with globaldata middleware
Route::group(['middleware' => ['globaldata']], function () {
// add routes that need to site_settings
}
In file - \vendor\autoload.php, define your gobals variable as follows, should be in the topmost line.
$global_variable = "Some value";//the global variable
Access that global variable anywhere as :-
$GLOBALS['global_variable'];
Enjoy :)
I know I am super late to the party, but this was the easiest way I found.
In app/Providers/AppServiceProvider.php, add your variables in the boot method. Here I am retrieving all countries from the DB:
public function boot()
{
// Global variables
view()->composer('*',function($view) {
$view->with('countries', Country::all());
});
}
There are two options:
Create a php class file inside app/libraries/YourClassFile.php
a. Any function you create in it would be easily accessible in all the views and controllers.
b. If it is a static function you can easily access it by the class name.
c. Make sure you inclued "app/libraries" in autoload classmap in composer file.
In app/config/app.php create a variable and you can reference the same using
Config::get('variable_name');
Hope this helps.
Edit 1:
Example for my 1st point:
// app/libraries/DefaultFunctions.php
class DefaultFunctions{
public static function getSomeValue(){
// Fetch the Site Settings object
$site_settings = Setting::all();
return $site_settings;
}
}
//composer.json
"autoload": {
"classmap": [
..
..
..
"app/libraries" // add the libraries to access globaly.
]
}
//YourController.php
$default_functions = new DefaultFunctions();
$default_functions->getSomeValue();

Resources