Laravel - pass parameters through app->bind to model's constructor - laravel

Well, code describes it all. I've an Entity Service Provider, which pass an instance of Playlist Model, which supposed to get an array as it's first constructor parameter. How to pass that parameter through app->bind? Knowing that EntityServiceProvider is automagically injected when referenced in the controller.
// Current Code
/**
* Playlist Entity
*
* #return Playlist\PlaylistEntity
*/
$this->app->bind('Playlist\PlaylistEntity', function($app)
{
return new PlaylistEntity($app->make('Playlist\PlaylistRepositoryInterface'));
});
// Should be something like this:
/**
* Playlist Entity
*
* #return Playlist\PlaylistEntity
*/
$this->app->bind('Playlist\PlaylistEntity', function($app)
{
return new PlaylistEntity($app->make('Playlist\PlaylistRepositoryInterface', $parameters));
});
Similar case: Laravel 4: Passing data from make to the service provider

Alex Russell's comment works for me as well.
The answer in the 'similar case' post is correct as far as I can tell. $this->app->bind('Whatever', function ($app, $params) { var_dump($params); }); followed by App::make('Whatever', [1, 2, 3]); var_dumps the [1, 2, 3] array for me.

In Laravel 5.4 the ability to pass config parameters when resolving from the container using App::make() was removed and subsequently re-implemented as App::makeWith().
BTW I tried to make this a comment on the previous answer, but it didn't let me. Maybe due to not enough experience points?

Thanks #yevgeniy-afanasyev for pointing out the problems when mocking. If you need to mock these instances, you could go for Taylor Otwell's early suggestion here: https://github.com/laravel/ideas/issues/391#issuecomment-285197048
I just needed that and it worked well. Just return a closure and call it, when you ::make.
// Service Provider
$this->app->bind(MyClass::class, function ($app) {
return function($param) : MyClass
{
return new MyClass($param);
}
}
// ::make
$myInstance = App::make(MyClass::class)($myParameter);
// mock
$myMock = Mockery::mock(new MyClass($myParameter));
$this->instance(MyClass::class, function($param) use ($myMock) { return $myMock; });

Related

How to overwrite bulk clone function for laravel backpack

I am trying to overwrite a bulk clone function.
I just want to clone some values, and the rest assign a static value. For example, I just want to clone the name and description values, and the date I want to assign it the current date.
And well I don't know how to do it.
use \Backpack\CRUD\app\Http\Controllers\Operations\BulkCloneOperation { bulkClone as traitBulkClone; }
public function bulkClone($id) {
// your custom code here
$this->traitBulkClone($id);
}
TLDR: The most efficient way would probably be to overwrite the replicate() method on your model. Note that it is not a Backpack method, but an Eloquent method that BulkCloneOperation uses to duplicate a particular entry.
WHY?
Inside the BulkCloneOperation that you're using, you'll notice the route calls the bulkClone() method, that itself is just making some calls to the replicate() method on the model. That means you have two options to override this behaviour:
(Option A). Override the bulkClone() method in your CrudController. This will override the behaviour only on that particular admin operation.
(Option B). Override the replicate() method in you Model. That way, any time replicate() is called (by your admin panel or any other part of your software), the duplication is done in the way you specified.
In most cases, I think Option B is more appropriate, since it would avoid future code duplication. Here's Laravel's replicate() method at this time, just copy-pasting it into your model and modifying it to fit your needs is the best solution, if you ask me:
/**
* Clone the model into a new, non-existing instance.
*
* #param array|null $except
* #return static
*/
public function replicate(array $except = null)
{
$defaults = [
$this->getKeyName(),
$this->getCreatedAtColumn(),
$this->getUpdatedAtColumn(),
];
$attributes = Arr::except(
$this->getAttributes(), $except ? array_unique(array_merge($except, $defaults)) : $defaults
);
return tap(new static, function ($instance) use ($attributes) {
$instance->setRawAttributes($attributes);
$instance->setRelations($this->relations);
$instance->fireModelEvent('replicating', false);
});
}

Routing & pretty URL

I am looking to create prettier URLs, and I'm having issues creating a valid route:
Let's say I have the following page http://localhost/app/account/5/edit.
This works fine with Route::get('account/{account}', 'AccountController#edit');
If I modify the Account Model and modify getRouteKeyName to return 'name', I am able to (with the same Route from above) access the following link: http://localhost/app/account/randomName/edit
The thing is, I am interested in a slightly different route, which is: http://localhost/app/account/randomName-5/edit
If I create a route Route::get('/accounts/{ignore}-{account}/edit', 'AccountController#edit'), it will fail as the first argument sent to edit is string and not an instance of Account. This can easily be fixed by modifying edit(Account $ac) to edit($ignored, Account $ac);... but it feels like cheating.
Is there a way to to get the route to ignore the first {block}?
According to the docs, you can customize your resolution logic for route model binding.
In this scenario, you can do something like this in App\Providers\RouteServiceProvider:
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
parent::boot();
Route::bind('accountNameWithId', function ($value) {
list($accountName, $accountId) = explode('-', $value);
return App\Account::whereKey($accountId)
->where('name', $accountName)
->firstOrFail();
});
}
Then you can redefine your route like this:
Route::get('account/{accountNameWithId}', 'AccountController#edit');

How to add an Action to Account Controller in Shopware

How to add a custom action to an existing Controller in Shopware?
Examples (url structure):
/account/bonus
/account/custom
/account/...
Usually it's easier and cleaner to create a new controller for that purpose, but in some cases it's necessary.
You should not replace the "account" controller.
You can define you own action for existing controller with following:
public static function getSubscribedEvents()
{
return [
'Enlight_Controller_Action_Frontend_Account_MyBonus' => 'onAccountMyBonus',
];
}
and then
public function onAccountMyBonus(\Enlight_Event_EventArgs $args)
{
$args->setProcessed(true);
.....
your code here
}
Spoiler: Replace the controller
There is no cleaner way than to replace the whole controller and extend it's functionality, so it's nearly as clean as Shopware's hooks.
Guide
Add a new Subscriber to your Plugin
class AccountSubscriber implements SubscriberInterface
{
/**
* #return array
*/
public static function getSubscribedEvents()
{
return array(
'Enlight_Controller_Dispatcher_ControllerPath_Frontend_Account' => 'getAccountController'
);
}
/**
* #return string
*/
public function getAccountController()
{
return $this->getPath() . '/Controllers/Frontend/AccountExtended.php';
}
/**
* #return string
*/
public function getPath()
{
$plugin = Shopware()->Container()->get('kernel')->getPlugins()['AcmeYourPlugin'];
return $plugin->getPath();
}
}
Downsides
Unfortunately some controller have private methods which impact the logic. Like the Account Controller. So it's not always possible to simply extend the controller.
In the end, try to add a new controller with a new route.
It's easier, and cleaner.
There is a cleaner way than replacing the whole Controller.
It is also not recommended to replace a whole controller due to the lack of update compatibility.
In the worst case something like that could kill the whole website.
A while ago I created a thread in the shopware forum (german) discussing the exact same issue. I wanted to extend an existing finishAction() in the checkout Controller.
public function onPostDispatchCheckout(\Enlight_Controller_ActionEventArgs $args)
{
/** #var \Enlight_Controller_Action $controller */
$controller = $args->getSubject();
/** #var \Enlight_Controller_Request_Request $request */
$request = $controller->Request();
if ($request->getActionName() !== 'finish') {
return;
}
// do your stuff here
}
So even though it is not the exact same issue you have, the procedure is quite the same.
First off you subscribe to the controller (in my case the PostDispatchCheckout Controller) afterwards you edit the controller in your Bootstrap.php
To make sure, that it just alters a specific action you have to use the if-construction so your code gets just triggered on the wished action [in my case the finishAction()].
I hope this helps. What wonders me though is why you have to add a new action to an already existing controller. I can think of no situation where something like that is more practicable than creating a complete new custom controller.
Kind regards,
Max

How do I access non id property of request inside form requests?

I want to access route request parameters inside laravel form requests authorize. I cant find an example describing this.
// Works fine when you want id
dd($this->route('myResourceName'));
// How to do when I want something else???
dd($this->route('anotherAttribute'));
// Above give null probably because it is a resourceful controller
On a side note, I dont understand this design, whats the point?
$this->route('anyAttribute') would be the easiest, right?
Edit: more extensive example
class UpdateSlotAPIRequest extends APIRequest
{
public function __construct(){
parent::__construct();
$this->slot = Slot::find($this->route('slot'));
$this->access_token = $this->route('access_token'); // this is not working!
}
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
// If administrator is logged in all is good.
// If slot is free its ok.
// If its not free but you provide good access_token its also fine.
return Auth::check() || $this->slot->isAvailable() || $this->slot->isValidAccessToken($this->access_token);
}
...
```
$access_token = request()->input('access_token');
Found it in https://laravel.com/docs/5.4/helpers

Is there any decent way to Decorate models returned from a Magento `[model]_load_after`event?

I'm trying to overwrite some methods in models, and I'm on a mission to avoid overwrites and rewrites of models for maximum compatibility with other modules.
I figured the best way would be to simply decorate models after they are loaded from Magento, however as far as I can tell because of the way the observer pattern in Magento is written it's impossible to accomplish this. ( As Magento always returns the reference to $this ), and the lack of interfaces might also cause trouble later down the road? See this partial of Mage/Core/Model/Abstract.php
/**
* Processing object after load data
*
* #return Mage_Core_Model_Abstract
*/
protected function _afterLoad()
{
Mage::dispatchEvent('model_load_after', array('object'=>$this));
Mage::dispatchEvent($this->_eventPrefix.'_load_after', $this->_getEventData());
return $this;
}
My question boils down to the title, is there a decent way of accomplishing this?, or am I simply stuck with rewrites :(?
The path I would like to take is;
On event [model]_load_after
return new Decorator($event->getObject())
Where the decorator class in my case would be something like;
public function __construct(Mage_Sales_Model_Order_Invoice $model)
{
parent::__construct($model); // sets $this->model on parent class, see below
}
// overwrite the getIncrementId method
public function getIncrementId()
{
return '12345';
}
// partial of parent class
public function __call($method, array $args)
{
return call_user_func_array(array($this->model, $method), $args);
}
And just some pseudo-code for extra clarification;
$model = Mage::getModel('sales/order_invoice')->load(1);
echo get_class($model);
Namespace_Decorator **INSTEAD OF** Mage_Sales_Model_...
echo $model->getIncrementId();
'12345' **INSTEAD OF** '1000001' ( or whatever the format might be )
Thanks for your time reading / commenting, I really hope there actually is a way to accomplish this in a clean fashion without making use of code overrides or rewrites of models.
Edit: extra clarification
Basically what I would like is to return an instance of the Decorator in a few cases, the sales_invoice being one of them and customer the other. So when any load() call is made on these models, it will always return the instance of the Decorator instead of the Model. Only method calls that the decorator overrides would be returned, and any other method calls would "proxied" through __call to the decorated object.
I'm not sure if I got your question right but here goes.
I think you can use the event [model]_load_after and simply do this:
$object = $event->getObject();
$object->setIncrementId('12345');
Or if you want to use a decorator class make it look like this:
public function __construct(Mage_Sales_Model_Order_Invoice $model)
{
parent::__construct($model);
$model->setIncrementId($this->getIncrementId());
}
public function getIncrementId()
{
return '12345';
}
I know that this is not exactly a decorator pattern but it should work.
I know that when adding a new method to the 'decorator' class you need to add it to attach data to the main model.
This is just my idea. I haven't got an other.
[EDIT]
You can try to rewrite the load method on the object to make it return what you need. But I wouldn't go that way. You can end up screwing a lot of other things.
I don't think there is an other way to do it because load always returns the current object no mater what you do in the events dispatched in the method. see Mage_Core_Model_Abstract::load()
public function load($id, $field=null)
{
$this->_beforeLoad($id, $field);
$this->_getResource()->load($this, $id, $field);
$this->_afterLoad();
$this->setOrigData();
$this->_hasDataChanges = false;
return $this;
}
By making it return new Decorator($this), you might achieve what you need, but just make sure that when calling $model->doSomething() and doSomething() is not a method in your decorator you still end up calling the original method on the model.

Resources