Add custom function to Laravel query builder - php

I am trying to add USE INDEX() to the query builder in Laravel. I tried to follow similar steps to link and was kind of successful but I cannot manage the last bit and I am not sure my ad-hoc code has created a huge backdoor.
The target: The target of my exercise is to add Index to the query builder like below:
DB::table('users')->where('id',1)->**useIndex**('users')->get()->first();
Here an option useIndex specifies the index that I am going to use for this query.
What I have done yet: Created a class named Connection in App/Override
<?php
namespace App\Override;
class Connection extends \Illuminate\Database\MySqlConnection {
//#Override
public function query() {
return new QueryBuilder(
$this,
$this->getQueryGrammar(),
$this->getPostProcessor()
);
}
}
Created a service provider named CustomDatabaseServiceProvider in App/Providers. Here I just manipulated registerConnectionServices function. I further commented Illuminate\Database\DatabaseServiceProvider::class, and added App\Providers\CustomDatabaseServiceProvider::class, to app.php in config directory.
<?php
namespace App\Providers;
use App\Override\Connection;
use Illuminate\Database\DatabaseManager;
use Illuminate\Database\Query\Grammars\Grammar;
use Illuminate\Database\Schema;
use Illuminate\Contracts\Queue\EntityResolver;
use Illuminate\Database\Connectors\ConnectionFactory;
use Illuminate\Database\Eloquent\Factory as EloquentFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\QueueEntityResolver;
use Illuminate\Support\ServiceProvider;
class CustomDatabaseServiceProvider extends ServiceProvider
{
/**
* The array of resolved Faker instances.
*
* #var array
*/
protected static $fakers = [];
/**
* Bootstrap the application events.
*
* #return void
*/
public function boot()
{
Model::setConnectionResolver($this->app['db']);
Model::setEventDispatcher($this->app['events']);
}
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
Model::clearBootedModels();
$this->registerConnectionServices();
$this->registerEloquentFactory();
$this->registerQueueableEntityResolver();
}
/**
* Register the primary database bindings.
*
* #return void
*/
protected function registerConnectionServices()
{
// The connection factory is used to create the actual connection instances on
// the database. We will inject the factory into the manager so that it may
// make the connections while they are actually needed and not of before.
$this->app->singleton('db.factory', function ($app) {
return new ConnectionFactory($app);
});
// The database manager is used to resolve various connections, since multiple
// connections might be managed. It also implements the connection resolver
// interface which may be used by other components requiring connections.
$this->app->singleton('db', function ($app) {
$dbm = new DatabaseManager($app, $app['db.factory']);
//Extend to include the custom connection (MySql in this example)
$dbm->extend('mysql', function ($config, $name) use ($app) {
//Create default connection from factory
$connection = $app['db.factory']->make($config, $name);
//Instantiate our connection with the default connection data
$new_connection = new Connection(
$connection->getPdo(),
$connection->getDatabaseName(),
$connection->getTablePrefix(),
$config
);
//Set the appropriate grammar object
// $new_connection->setQueryGrammar(new Grammar());
// $new_connection->setSchemaGrammar(new Schema\());
return $new_connection;
});
return $dbm;
});
$this->app->bind('db.connection', function ($app) {
return $app['db']->connection();
});
}
/**
* Register the Eloquent factory instance in the container.
*
* #return void
*/
protected function registerEloquentFactory()
{
$this->app->singleton(FakerGenerator::class, function ($app, $parameters) {
$locale = $parameters['locale'] ?? $app['config']->get('app.faker_locale', 'en_US');
if (!isset(static::$fakers[$locale])) {
static::$fakers[$locale] = FakerFactory::create($locale);
}
static::$fakers[$locale]->unique(true);
return static::$fakers[$locale];
});
$this->app->singleton(EloquentFactory::class, function ($app) {
return EloquentFactory::construct(
$app->make(FakerGenerator::class), $this->app->databasePath('factories')
);
});
}
/**
* Register the queueable entity resolver implementation.
*
* #return void
*/
protected function registerQueueableEntityResolver()
{
$this->app->singleton(EntityResolver::class, function () {
return new QueueEntityResolver;
});
}
}
and finally created a class named QueryBuilder in App/Override. this is the problematic class:
<?php
namespace App\Override;
use Illuminate\Support\Facades\Cache;
class QueryBuilder extends \Illuminate\Database\Query\Builder
{
private $Index = [];
public function useIndex($index = null)
{
$this->Index = $index;
return $this;
}
//#Override
public function get($columns = ['*'])
{
if ($this->Index) {
//Get the raw query string with the PDO bindings
$sql_str = str_replace('from `' . $this->from . '`', 'from `' . $this->from . '` USE INDEX (`' . $this->Index . '`) ', $this->toSql());
$sql_str = vsprintf($sql_str, $this->getBindings());
return parent::get($sql_str);
} else {
//Return default
return parent::get($columns);
}
}
}
The issues here are:
The output does not contain USE INDEX
Is it safe to use str_replace to manipulate query?

The query builder is macroable so in your service provider you can probably do:
Illuminate\Database\Query\Builder::macro(
'tableWithIndex',
function ($table, $index) {
$table = $this->grammar->wrapTable($table);
$index = $this->grammar->wrap($index);
return $this->fromRaw("$table USE INDEX ($index)");
}
);
Then you could use this:
DB::tableWithIndex('users', 'users');
within the macro $this would refer to the query builder instance
Note that I have them both in one because you can potentially have multiple from calls for the same query and it would be a mess trying to figure out what goes where

Related

Extending Laravel/Lumen Query Builder to automagically add SQL comments

I use a large RDS database instance that is shared among several different projects (not microservices to be exact) and this database's performance is critical. Hence I monitor the queries whenever support team raise tickets related to performance of our services. So in order for me to track where each query originated from i.e, which app, file and line number, I want to automatically add a SQL comment for all queries. So when I call toSql() on the query builder object it must show me the comment
-- lumen-api:app/Http/Controllers/APIController.php:85
select * from users;
env(app_name) . ':'. __FILE__ . ':' . __LINE__.
I tried to extend query builder and grammar classes and bind them to the service container but I think I'm doing something wrong. Please take a look at my implementation of how I extended those classes.
<?php
// app/Classes/Database/Query/Grammars/QueryGrammar.php
namespace App\Classes\Database\Query\Grammars;
use App\Classes\Database\Query\QueryBuilder;
use Illuminate\Database\Query\Grammars\Grammar;
class QueryGrammar extends Grammar
{
/**
* #param QueryBuilder $query
* #param $comment
* #return string
*/
public function compileComment(QueryBuilder $query, $comment)
{
$this->selectComponents[] = 'comment';
return '-- ' . $comment . PHP_EOL;
}
}
<?php
// app/Classes/Database/Query/QueryBuilder.php
<?php
namespace App\Classes\Database\Query;
use Illuminate\Database\Query\Builder;
class QueryBuilder extends Builder {
/**
* #param $comment
* #return $this
*/
public function comment($comment): QueryBuilder
{
$this->comment = $comment;
return $this;
}
}
<?php
//app/Providers/AppServiceProvider.php
<?php
namespace App\Providers;
use App\Classes\Database\Query\Grammars\QueryGrammar;
use App\Classes\Database\Query\QueryBuilder;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Database\Query\Builder;
use Illuminate\Database\Query\Grammars\Grammar;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* #return void
*/
public function register()
{
/**
* Extending Query Builder to support SQL comments
*/
$this->app->bind(Grammar::class, function () {
return new QueryGrammar();
});
$this->app->bind(Builder::class, function () {
return new QueryBuilder(/* how to send params?*/);
});
}
}
I know this implementation is not for auto adding the sql comments. So when I use this in my controller:
return Admin::where('login_email','bhargav.nanekalva#mpokket.com')->comment(__FILE__ . __LINE__)->toSql();
Laravel throws the following error: (which means the binding didn't happen)
(1/1) BadMethodCallException
Call to undefined method Illuminate\Database\Eloquent\Builder::comment()
what I need help is for
The right way to modify these classes
automatically adding the sql comment
Overriding the Grammer class directly may be possible but it internally delegates its work to Database Specific grammer classes
For example if you have configured Mysql in config/database.php then the Grammer class delegates the work on to Illuminate\Database\Query\Grammars\MySqlGrammar
Similarly for Postgres it will be Illuminate\Database\Query\Grammars\PostgresGrammar
Based on the database config the
ConnectionFactory[src/Illuminate/Database/Connectors/ConnectionFactory.php->createConnection()]
loads the proper connection manager for a given database
I am not sure if overriding of this classes is even possible or not because of the PSR-4 loading as the namespace is tightly linked with the physical location of the file in the directory tree
So instead of that I would suggest to go for laravel macros by which you may add new functions to existing classes that use Macroable trait
A POC example can be found below, for further advancement you are encouraged to dig the code for update, insert, delete etc in Grammer.php and Builder.php
<?php
namespace App\Providers;
use Illuminate\Support\Arr;
use Illuminate\Support\ServiceProvider;
use DB;
use Illuminate\Database\Query\Builder;
use Illuminate\Database\Query\Grammars\Grammar;
class AppServiceProvider extends ServiceProvider
{
public function register()
{
Grammar::macro("T_compileSelect", function (Builder $query) {
if ($query->unions && $query->aggregate) {
return $this->compileUnionAggregate($query);
}
$original = $query->columns;
if (is_null($query->columns)) {
$query->columns = ['*'];
}
$sql = trim($this->concatenate(
$this->compileComponents($query))
);
if ($query->unions) {
$sql = $this->wrapUnion($sql).' '.$this->compileUnions($query);
}
$query->columns = $original;
return $sql . ' -- ' . (!empty($query->comment)?$query->comment:'');
});
Builder::macro("T_toSql", function () {
$str = $this->grammar->T_compileSelect($this);
return $str;
});
Builder::macro("T_runSelect", function () {
$str = $this->connection->select(
$this->T_toSql(), $this->getBindings(), ! $this->useWritePdo
);
return $str;
});
Builder::macro("addComment", function ($comment, $columns = ['*']) {
$this->comment = $comment;
$res = collect($this->onceWithColumns(Arr::wrap($columns), function () {
return $this->processor->processSelect($this, $this->T_runSelect());
}));
return $res;
});
}
public function boot()
{
}
}
Usage:
Admin::where('login_email','bhargav.nanekalva#mpokket.com')
->addComment(__FILE__ . __LINE__)
->get();

How to bind configuration value Laravel service provider on run-time?

I have created custom service provider which extends XeroServiceProvide, Basically, I have multiple Xero Account and I want to change two configuration params value runtime consumer_key and consumer_secret. Is there a quick way. I have checked Service Container contextual binding but don't know how to use.
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use DrawMyAttention\XeroLaravel\Providers\XeroServiceProvider;
class CustomXeroServiceProvider extends XeroServiceProvider
{
private $config = 'xero/config.php';
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot()
{
//
parent::boot();
}
/**
* Register the application services.
*
* #return void
*/
public function register($configParams = [])
{
parent::register();
if(file_exists(config_path($this->config))) {
$configPath = config_path($this->config);
$config = include $configPath;
}
$this->app->bind('XeroPrivate', function () use ($config,$configParams) {
if(is_array($configParams) && count($configParams) > 0){
if(isset($configParams['consumer_key'])){
$config['oauth']['consumer_key'] = $configParams['consumer_key'];
}
if(isset($configParams['consumer_secret'])){
$config['oauth']['consumer_secret'] = $configParams['consumer_secret'];
}
}
return new \XeroPHP\Application\PrivateApplication($config);
});
}
}
From Controller I tried changing value like this but bind params not changing dynamically
foreach($centers as $center) {
config(['xero.config.oauth.consumer_key' => $center->consumer_key]);
config(['xero.config.oauth.consumer_secret' => $center->consumer_secret]);
}
Update 2
Is there a way I can rebind service container after updating config file values or somehow i can refresh service provider binding?
config(['xero.config.oauth.consumer_key' => 'XXXXXXX']);
config(['xero.config.oauth.consumer_secret' => 'XXXXXX']);
// rebind Service provider after update
This is how I ended up doing. I have created a custom function that sets value on runtime.
/**
* connect to XERO by center
* #param $center
* #return mixed
*/
public static function bindXeroPrivateApplication($center){
$configPath = config_path('xero/config.php');
$config = include $configPath;
config(['xero.config.oauth.consumer_key' => $center->consumer_key]);
config(['xero.config.oauth.consumer_secret' => $center->consumer_secret]);
return \App::bind('XeroPrivate', function () use ($config,$center) {
$config['oauth']['consumer_key'] = $center->consumer_key;
$config['oauth']['consumer_secret'] = $center->consumer_secret;
return new \XeroPHP\Application\PrivateApplication($config);
});
}
I have model called Center.php and I am calling above function from that same model as below.
$center = Center::find(1);
self::bindXeroPrivateApplication($center);

Laravel Event listener and caching not working

I am facing some difficulties while developing an app on Laravel.
I want to use Event and Listener to delete and rebuild the cache of an object.
Here is the code:
app\Events\CampaignEvent.php
namespace App\Events;
use Illuminate\Queue\SerializesModels;
class CampaignEvent extends Event
{
use SerializesModels;
public $user_id;
public $cache_keys;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct($user_id, $cache_keys)
{
$this->user_id = $user_id;
$this->cache_keys = $cache_keys;
}
}
app\Listenters\CampaignListener.php
<?php
namespace App\Listeners;
use App\Events\CampaignEvent;
use Cache;
use Log;
use App\BrandCampaign;
class CampaignListener
{
/**
* Handle the event.
*
* #param CampaignEvent $event
* #return void
*/
public function handle(CampaignEvent $event)
{
/**
* Remove cache
*/
if(is_array($event->cache_keys)){
foreach($event->cache_keys as $index => $cache_key){
\Cache::forget($cache_key);
Log::debug("[CACHE] Deleted cache for: " . $cache_key);
}
} else {
\Cache::forget($event->cache_keys);
Log::debug("[CACHE] Deleted cache for: " . $event->cache_keys);
}
/**
* Rebuild cache for BrandCampaigns
*/
$campaigns = BrandCampaign::with(['influencers' => function($query){
$query->with(['influencer' => function($query){
$query->select('id','profile_picture');
}])->latest();
}])->where('user_id', $event->user_id )->latest()->get();
$total_influencers = [];
foreach($campaigns as $campaign){
foreach ($campaign->influencers as $influencer) {
if(!in_array($influencer->influencer_id, $total_influencers))
$total_influencers[] = $influencer->influencer_id;
}
}
$total_influencers = count($total_influencers);
$campaigns = collect($campaigns)->toArray();
\Cache::forever('#suppliers_campaigns('.$event->user_id.')', $campaigns);
\Cache::put('#suppliers_total_campaigns('.$event->user_id.')', $total_influencers, 10);
Log::debug("[CACHE] Cache rebuilt successfully!");
return $event;
}
}
I want to cache an array "forever", but in my campaign controller, after the event is fired, when I pull the array from cache it is returning null
Thanks!
Works in Laravel 5 (based on the question) & Laravel 7 (latest) as well.
use Illuminate\Support\Facades\Cache;
// Remove cache
Cache::forget('brandCampaigns');
// Rebuild cache for BrandCampaigns. Here, when the cache key doesn't exists, the function will be called and the returned value will be stored in the cache
$campaigns = Cache::rememberForever('brandCampaigns', function () {
return BrandCampaign::with(['influencers' => function ($query) {
$query->with(['influencer' => function ($query) {
$query->select('id', 'profile_picture');
}])->latest();
}])->where('user_id', $event->user_id)->latest()->get();
});
It is important to enable discovery in EventServiceProvider class.
-> app/Providers/EventServiceProvider.php
public function shouldDiscoverEvents()
{
return true;
}
make sure this function return true, otherwise events and listeners don't find together.

Laravel user capabilities

Within Laravel you can easily define abilities and then hook into them later on a user request regarding to do different actions:
$gate->define('update-post', function ($user, $post) {
return $user->id === $post->user_id;
});
But almost all my defined abilities has this part $user->id === $model->user_id in it. I don't like it as it's a kind of repeating a condition over and over which I think could be more abstract.
Most of my defined abilities are according to updating/deleting records, so it would be better if I could make a global condition applied to all of them or if there could be a group ability defining which is like to what we do in routing.
Is there any workaround for it? I really like it DRY.
Everything in Laravel is extendable, that's the power of its service providers.
You can extend the Gate object to a MyCustomGate object and do whatever you want in that object. Here's an example:
MyCustomGate.php
class MyCustomGate extends \Illuminate\Auth\Access\Gate
{
protected $hasOwnershipVerification = [];
/**
* Define a new ability.
*
* #param string $ability
* #param callable|string $callback
* #return $this
*
* #throws \InvalidArgumentException
*/
public function defineWithOwnership($ability, $callback, $foreignUserIdKey = "user_id")
{
// We will add this
$this->hasOwnershipVerification[$ability] = $foreignUserIdKey;
return $this->define($ability, $callback);
}
/**
* Resolve and call the appropriate authorization callback.
*
* #param \Illuminate\Contracts\Auth\Authenticatable $user
* #param string $ability
* #param array $arguments
* #return bool
*/
protected function callAuthCallback($user, $ability, array $arguments)
{
$callback = $this->resolveAuthCallback(
$user, $ability, $arguments
);
// We will assume that the model is ALWAYS the first key
$model = is_array($arguments) ? $arguments[0] : $arguments;
return $this->checkDirectOwnership($ability, $user, $model) && call_user_func_array(
$callback, array_merge([$user], $arguments)
);
}
/**
* Check if the user owns a model.
*
* #param string $ability
* #param \Illuminate\Contracts\Auth\Authenticatable $user
* #param \Illuminate\Database\Eloquent\Model $model
* #return bool
*/
protected function checkDirectOwnership($ability, $user, $model)
{
if(!isset($this->hasOwnershipVerification[$ability])) {
return true
}
$userIdKey = $this->hasOwnershipVerification[$ability];
// getAuthIdentifier() is just ->id, but it's better in case the pk of a user is different that id
return $user->getAuthIdentifier() == $model->{$userIdKey};
}
}
Then, you will have to tell Laravel to use your gate instead of the default one. You ca do that in your AuthServiceProvider (assuming that it's extending Illuminate\Auth\AuthServiceProvider, just add the following method.
AuthServiceProvider
/**
* Register the access gate service.
*
* #return void
*/
protected function registerAccessGate()
{
$this->app->singleton(\Illuminate\Contracts\Auth\Access\Gate::class, function ($app) {
return new MyCustomGate($app, function () use ($app) {
return $app['auth']->user();
});
});
}
And this way, you can define abilities using defineWithOwnership() method instead of define(). You can still use define() for abilities that don't require ownership verification. There's a third parameter defineWithOwnership() accepts which is $foreignUserIdKey; that's used for the case when a model has a different field for the user id.
Note: I wrote the code on the fly and did not try it, it may have errors, but you get the idea.
I checked your question quite a bit, but I've found no "easy" way to do it.
Instead, what I would probably do is this:
<?php
namespace App\Policies;
use App\User;
use App\Post;
trait CheckOwnership {
protected function checkOwnership($user, $model) {
$owned = $user->id === $model->user_id;
if ($owned === false)
throw new NotOwnedException;
}
}
class PostPolicy
{
use CheckOwnership;
public function update(User $user, Post $post)
{
try {
$this->checkOwnership($user, $post);
//continue other checks
} catch (NotOwnedException $ex) {
return false;
}
}
}
Add this function to your AuthServiceProvider
public function defineAbilities(array $abilities, $gate)
{
foreach($abilities as $name => $model){
$gate->define($name, function ($user, $model){
return $user->id === ${$model}->user_id;
});
}
}
and then inside boot method
$this->defineAbilities(['ability1' => 'model1', 'ability2' => 'model2'], $gate);
You can define another function and call it within the anonymous function. This will allow you to have commonly-used code in one central location while still allowing any resource-specific logic.
Add this function to your AuthServiceProvider class:
public function userCheck(User $user, $target)
{
// do the user id check
$result = isset($target->user_id) && isset($user) && $user->id === $target->user_id;
return $result;
}
Your code, modified:
$gate->define('update-post', function ($user, $post) {
// call the function
$result = $this->userCheck($user, $post);
// do some kind of 'update-post' specific check
return $result/* && some_bool_statement*/;
});
I think you can use middlewares.
Simply make a admin middleware and use it in your routes and routes group.
And there is no security bug on your project (delete, create & ... actions) because Laravel has csrf token!
You can use before() function, also.
And then an important note:
if you don't define a correspond function on Policy class and call it $this->authorize($post) on a controller an unauthorized Action error will be thrown unless before()methodreturnstrue.
for example call $this->authorize on Dashboard\PostsController:
public function edit($id)
{
$post = Post::find($id)->first();
$this->authorize($post);
return view('dashboard.post')->with(compact('post'));
}
and if we defined a PostPolicy Class:
class PostPolicy
{
use HandlesAuthorization;
public function before($user, $ability)
{
return $user->is_admin;
}
}
If user be admin he/she can edit post because we returned true in before() method despite of have not a method with same name (as edit method in PostsController).
In fact Laravel will check for before method mthod on Policy Class. if before return'snull will check for correspond method with same name on controller method and if this method not found user cannot perform action.
Thank you laravel for DRY us!♥

Laravel ioc automatic resolution - works from controller but not from custom class

Namespaces omitted for brevity...
I have written the following service provider and registered in config/app.php:
class OfferServiceProvider extends ServiceProvider
{
public function register()
{
$this->registerLossControlManager();
}
protected function registerLossControlManager()
{
$this->app->bind('LossControlInterface', 'LossControl');
}
}
Here is my LossControlInterface
interface LossControlInterface
{
/**
* #param int $demandId
* #param float $offerTotal
* #param float $productTotal
* #param null|int $partnerId
* #return mixed
*/
public function make($demandId, $offerTotal, $productTotal, $partnerId = null);
/**
* #return float
*/
public function getAcceptableLoss();
/**
* #return bool
*/
public function isAcceptable();
/**
* #return bool
*/
public function isUnacceptable();
/**
* #return null
*/
public function reject();
}
Now within the controller, I can inject the LossController as follows:
use LossControlInterface as LossControl;
class HomeController extends BaseController {
public function __construct(LossControl $lossControl)
{
$this->lossControl = $lossControl;
}
public function getLossThresholds()
{
$lossControl = $this->lossControl->make(985, 1000, null);
var_dump('Acceptable Loss: ' . $lossControl->getAcceptableLoss());
var_dump('Actual Loss: ' . $lossControl->calculateLoss());
var_dump('Acceptable? ' . $lossControl->isAcceptable());
}
}
However if I try to dependency inject the LossControlInterface from within a custom class called by a command:
[2014-09-02 13:09:52] development.ERROR: exception 'ErrorException' with message 'Argument 11 passed to Offer::__construct() must be an instance of LossControlInterface, none given, called in /home/vagrant/Code/.../ProcessOffer.php on line 44 and defined' in /home/vagrant/Code/.../Offer.php:79
It appears as though I am unable to dependency inject the interface into a custom class, but I can when dependency injecting into a controller.
Any thoughts on what Im doing wrong or have omitted to get the automatic resolution working?
The IoC is automatic within controllers, and you don't see the injection because Laravel handles the construction of controllers for you. When creating any other custom class by using the new keyword, you will still need to send in all of the parameters needed to it's constructor:
$myClass = new ClassWithDependency( app()->make('Dependency') );
You can hide this, to a degree, by funneling creation of your custom class through a service provider:
// Your service provider
public function register()
{
$this->app->bind('ClassWithDependency', function($app) {
return new ClassWithDependency( $app->make('Dependency') );
});
}
Then just have the IoC make it whenever you need it:
$myClass = app()->make('ClassWithDepenency');
In your case, you can change your code to look like this:
private function setOffer(Offer $offer = null) {
$this->processOffer = $offer ?:
new Offer( app()->make('LossControlInterface') );
}
A perhaps cleaner approach could be to create a service provider and an OfferFactory which gets injected into your controller. The controller can then request the factory to create the offer whenever it needs one:
// Controller
public function __construct(OfferFactory $offerFactory)
{
$this->offerFactory = $offerFactory;
}
public function setOffer(Offer $offer = null)
{
$this->processOffer = $offer ?: $this->offerFactory->createOffer();
}
// OfferFactory
class OfferFactory
{
public function createOffer()
{
return app()->make('Offer');
}
}
This has the benefit of completely decoupling your controller from the logic behind the creation of the offer, yet allowing you to have a spot to add any amount of complexity necessary to the process of creating offers.
In Laravel 5.2 the simplest solution for your particular problem would be to replace
new Offer();
with
App::make('Offer');
or even shorter
app('Offer');
which will use Laravel Container to take care of dependencies.
If however you want to pass additional parameters to the Offer constructor it is necessary to bind it in your service provider
App::bind('Offer', function($app, $args) {
return new Offer($app->make('LossControl'), $args);
});
And voila, now you can write
app('Offer', [123, 456]);
In laravel 5.4 (https://github.com/laravel/framework/pull/18271) you need to use the new makeWith method of the IoC container.
App::makeWith( 'App\MyNameSpace\MyClass', [ $id ] );
if you still use 5.3 or below, the above answers will work.

Categories