Adding a new method to a Laravel vendor class - laravel

I'm fairly new to Laravel and I want to add a method to a vendor class. I'm sure it's just my unfamiliarity with how Laravel works, so I'm hoping there's a pretty easy solution.
I've installed a package (https://github.com/kawax/laravel-amazon-product-api) and want to add a new method that I can call like:
use App\Repositories\AmazonSearch\AmazonSearch;
$response = AmazonSearch::alter('All');
So I created a new folder app/Repositories/AmazonSearch and extended the AmazonClient class:
<?php
namespace App\Repositories\AmazonSearch;
use Revolution\Amazon\ProductAdvertising\AmazonClient;
class AmazonSearch extends AmazonClient {
/**
* {#inheritdoc}
*/
public function alter(...)
...
}
I guess I'm not sure on exactly what I need to do to be able to have this class instantiated like the original and use this new method.
Should I be creating a new service provider that would instantiate the new class? Can the existing one (https://github.com/kawax/laravel-amazon-product-api/blob/master/src/Providers/AmazonProductServiceProvider.php) just be extended?
There's some other answers here but many of them are for older Laravel versions. I'm not sure how to approach it the Laravel 8 way.
And I'm still fuzzy on how Laravel does all this, so thanks for your patience and any assistance you can provide.
EDIT: Well, I just renamed the class to ExtendedAmazonClient and added a facade and it seems to work now.
namespace App\AmazonSearch;
use Illuminate\Support\Facades\Facade;
use Revolution\Amazon\ProductAdvertising\AmazonClient;
class AmazonSearch extends Facade
{
/**
* Get the registered name of the component.
*
* #return string
*/
protected static function getFacadeAccessor()
{
return ExtendedAmazonClient::class;
}
}
trait Alter {
/**
* {#inheritdoc}
*/
public function alter(string $str) {
dd($str);
}
}
class ExtendedAmazonClient extends AmazonClient {
use Alter;
}
Can someone explain to me why the facade was the key?

To answer my own question for anyone else in the future: Facades is another way to use classes without manually creating an object. They are just a shortcut to classes registered by Laravel container.
https://stackoverflow.com/a/48843414/8749507

Related

Dependency Injection into a service in Laravel

I have the following code which I try to create an instance of Guzzle in my service class. I am not sure what is the proper way to Inject this dependency in Laravel, this is my first time using Laravel, I have used Symfony like 8 years ago.
I get a Runtime Exception
RuntimeException : A facade root has not been set.
My code
namespace App\Services;
use Illuminate\Support\Facades\Http;
class DataForSeoService
{
/**
* #var \Illuminate\Http\Client\PendingRequest
*/
private ?\Illuminate\Http\Client\PendingRequest $V3http = null;
public function __construct() {
$this->V3http = Http::withBasicAuth('xxx', 'xxx');
}

Target [Illuminate\Database\Eloquent\Model] is not instantiable while building

Peeps, I'm lost. Tried everything and after 5 hours of searching through the 10th page of Google hits, I give up. Maybe I just dont know how to ask Google the correct keywords..
I have this scenario: In lumen app, lets call it X, I have require custom packages CRUD and Storage, Storage is using functionality of CRUD.
StorageService has:
use Crud\Services\BaseService;
class StorageService extends BaseService{}
And Crud\BaseService has constructor, that uses Model:
use Illuminate\Database\Eloquent\Model;
class BaseService
{
protected $model;
public function __construct(Model $model)
{
$this->model = $model;
}
}
When I try to do anything with my app X, I get error:
Target [Illuminate\Database\Eloquent\Model] is not instantiable while building [Lumee\Storage\Services\StorageService]
I cannot get my head around how to get to proper class of Model, since I saw, that Model is abstract class.
Also, I'm using this CRUD package successfully in another App, only difference is, there CRUD is used directly in app, not via some other package. I'm confused, why there is working without any additional bindings and service registering..
EDIT: Added some binding into StorageServiceProvider (boot and register methods):
$this->app->bind(BaseService::class, function(){
return new BaseService(new Model());
});
And registered StorageServiceProvider in my boostrap/app.php:
$app->register(Storage\Providers\StorageServiceProvider::class);
Thing still returns same error. I tried with binding in CrudServiceProvider, nope.
you can't get object from abstract class (Model class) to solve this try this :
use Illuminate\Database\Eloquent\Model;
class BaseService
{
protected $model;
}
suppose your model is (Storage) :
use Crud\Services\BaseService;
class StorageService extends BaseService{
public function __construct(Storage $model)
{
$this->model = $model;
}
}

Laravel responsibility in the classes

I have a project on Laravel and need to do refactoring. I've read about Service provider and Dependency injection and have some questions.
This is a short structure: user model, event model, favorite user model and etc. Also, there are controllers for all models. Every event has a creator and client (user relationship). In every class, I am injecting appropriate service: User Service, Event service, Favorite user service and etc.
Let's consider the example - I want to delete the user:
class UserController extends Controller
{
/**
* #var UserService $userService
*/
protected $userService;
/**
* UserController constructor.
* #param UserService $userService
*/
public function __construct(UserService $userService)
{
$this->userService = $userService;
}
protected function delete(int $id)
{
$user = User::find($id);
if ($user) {
$this->userService->setUser($user);
$this->userService->delete();
}
}
Inside User service, I am processing user deleting - update the appropriate field. Also, I need to cancel all user events and delete favorite users.
My question is where should I do it? Should I inject event and favorite user service in UserController or in UserService? Or maybe there is a better way to do this action. Thx in advance
Seems like you have many actions depending on deleting user, so I would consider using Events and inside each listener handle the specifics of it.
class UserController extends Controller
{
/**
* #var UserService $userService
*/
protected $userService;
/**
* UserController constructor.
* #param UserService $userService
*/
public function __construct(UserService $userService)
{
$this->userService = $userService;
}
protected function delete(int $id)
{
if(!$this->userService->delete($id)) {
// return some error;
}
event(new UserWasRemoved($id));
// return success response
}
class DeleteUserService {
protected $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function delete($id){
return $this->user->delete($id);
}
}
// app/Providers/EventServiceProvider
class EventServiceProvider extends ServiceProvider
{
/**
* The event listener mappings for the application.
*
* #var array
*/
protected $listen = [
UserWasRemoved::class => [
CancelUserEvents::class,
RemoveUserFavorites::class,
// etc...
],
];
}
if deleting a user is much code, I will create DeleteUserService class which will contain all the code needed to delete a user and the effects of the delete.
class DeleteUserService {
public function __construct(int $userId)
{
$this->userId = $userId;
}
public function delete(){
$this->deleteUser();
$this->updateAppropriateFields(); // of course the name should be clearer
$this->deleteEvents();
$this->deleteFavoriteUser();
...
}
private function deleteUser(){...}
private function updateAppropriateFields(){...}
private function deleteEvents(){...}
private function deleteFavoriteUser(){...}
...
}
and in your controller either you inject the service or instantiate a new instance in the controller method
class UserController extends Controller
{
...
public function delete(int $id)
{
$user = User::findOrFail($id);
$deleteService = new DeleteUserService($user->id);
$deleteService->delete();
}
}
it's always a good idea to break your large function into one or more classes.
I suggest you abandon your approach to using services like this. Everything that you implement with services has already been implemented in Laravel, only even easier. You are now implementing more cumbersome logic on top of a simple, ready-made one.
For each object of your subject area (user, event, favorite user) add model class. Add in them the information of tables, the data from which belong to them - unless of course you use relational storage Eloquent Model Conventions. Here I have a question for you - does the favorite user entity need a separate class? If the User and the FavoriteUser have the same characteristics (that is, class members in the implementation), then there is no need to distribute them into different classes, and it is enough to add an additional isFavourite() (bool) attribute - in the class and in the table.
Implement the necessary methods in the controllers for each of your model classes as described in the documentation Defining Controllers. Depending on the type of the client part, the return of the response can be either JSON for the RESTful API, or a blade template with the transmitted data Views. Here, in the controller, you should implement a method to delete the model.
If you do not want the logic to be similar, that is, get rid of the similar methods all(), get(), post(), put(), delete() and others for UserController, for EventController, ... (with the exception of model classes - which will be different), then I advise you use the following architectural trick (this is optional, of course). Develop a universal layer - a class of a universal model, a class of a universal controller, a class of a universal model repository (if you use it in development). And in the controller, describe the common logic for all model classes, all(), get(), post(), put(), delete(). And then inherit each concrete class of the model from the universal, each concrete class of the controller from the universal - and so on. But!
In a concrete class of the model, it is necessary, for example, in an array, to list the attributes of the relational storage table, where you get the data from; it is also necessary to specify the name of the class in the variable - so that the controller can understand which class it should work with.
And in the controller in any way pass data about the model class - for example, using DependencyInjection Dependency Injection & Controllers.
With this approach, the classes of concrete controllers become thin, and the increase in code in them occurs only due to the redefinition of universal methods or the implementation of custom ones.
The advantage of this approach is also that there is no need to add routes of a similar structure. For example, a universal route will suffice for you
Route::get('{entity}/{id}', function ($entity, $id) {
$module = ucfirst($entity);
Route::get("{$entity}/{$id}", "{$module}Controller#get");
});
instead of many of the same type
Route::get('user/{id}', 'UserController#get');
Route::get('event/{id}', 'EventController#get');
and the like.

How to get underlying class name from Facade name in Laravel

I am finding it a bit difficult to understand Facades. Particularly how to find the underlying class name/location from a facade name. I have gone through the documentation but still not clear. For example, when using Auth::login()
, i found that there is no login() method in the Auth facade.
class Auth extends Facade
{
/**
* Get the registered name of the component.
*
* #return string
*/
protected static function getFacadeAccessor()
{
return 'auth';
}
/**
* Register the typical authentication routes for an application.
*
* #return void
*/
public static function routes()
{
static::$app->make('router')->auth();
}
}
The Auth facades getFacadeAccessor() method returns a string auth. But to which auth class should i be looking at? How to resolve the actual class?
Thanks,
You can use getFacadeRoot()
For example
$object = Auth::getFacadeRoot() // Illuminate\Auth\AuthManager instance
or to get the fully qualified class name
$class = get_class(Auth::getFacadeRoot()) // 'Illuminate\Auth\AuthManager'
Also you can use the container to resolve a class by it's accessor. This is what Laravel does under the hood when resolving a Facade.
$object = resolve('auth'); // Illuminate\Auth\AuthManager instance
Somewhere in a Serviceprovider the auth key is registered to something. For the auth key that's in vendor/laravel/frameworksrc/Illuminate/Auth/AuthServiceProvider.php. You can see that in the registerAuthenticator() method, the auth key is registered to the Illuminate\Auth\AuthManager with a singleton pattern.
The container has several ways to bind a key to a specific class. methods like bind and singleton for example. Facades are just an extra class to call the main class statically from the root namespace.
If you want to check out which class is used, you can use the following code: get_class(resolve('auth')). Ofcourse, you can replace auth with any string you want to check.
Bonus: I think you can override this behaviour by registering your own manager in some kind of way. I would advise you to extend the normal AuthManager and overwrite the methods that you want to see changed.
One option is to utilise the #see annotations on the facade
/**
* #see \Illuminate\Auth\AuthManager
* #see \Illuminate\Contracts\Auth\Factory
* #see \Illuminate\Contracts\Auth\Guard
* #see \Illuminate\Contracts\Auth\StatefulGuard
*/
class Auth extends Facade
Usually the method should exist on these classes/interfaces
For example, Auth::check() exists on \Illuminate\Contracts\Auth\Guard::check().
If you use an editor that allows you to follow through these definitions it can be a bit easier to traverse. Usually there's only one #see annotation so it's pretty easy to find the class.
You can use getFacadeRoot() on the package/service to get its object:
For example, I write a helper function to get Facade object so I can use that Cart object with ease from anywhere:
function cart() {
return \Gloudemans\Shoppingcart\Facades\Cart::getFacadeRoot();
}

Laravel 4 facade class not calling functions on facade root class

I've set up a package in laravel 4 via the artisan workbench command. I created a facade class and followed this tutorial to come up with the following service provider, facade and root classes:
src/Spoolphiz/Infusionsoft/InfusionsoftServiceProvider.php:
namespace Spoolphiz\Infusionsoft;
use Illuminate\Support\ServiceProvider;
class InfusionsoftServiceProvider extends ServiceProvider {
protected $defer = false;
/**
* Bootstrap the application events.
*
* #return void
*/
public function boot()
{
$this->package('spoolphiz/infusionsoft');
}
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
// Register 'infusionsoft' instance container to our Infusionsoft object
$this->app['infusionsoft'] = $this->app->share(function($app)
{
return new Spoolphiz\Infusionsoft\Infusionsoft;
});
}
/**
* Get the services provided by the provider.
*
* #return array
*/
public function provides()
{
return array();
}
}
src/Spoolphiz/Infusionsoft/Facades/Facade.php:
namespace Spoolphiz\Infusionsoft\Facades;
use Illuminate\Support\Facades\Facade;
class Infusionsoft extends Facade {
/**
* Get the registered name of the component.
*
* #return string
*/
protected static function getFacadeAccessor() { return 'infusionsoft'; }
}
Finally I've set up the underlying class thats to be connected to the facade at src/Spoolphiz/Infusionsoft/Infusionsoft.php:
namespace Spoolphiz\Infusionsoft;
//use Spoolphiz\Infusionsoft\iSDK;
/*
This is hackish and a un-laravel way to handle the requirement of \iSDK but unfortunately the xmlrpc3.0 lib doesn't want to correctly encode values when run with a namespace. Will try to resolve this later.
*/
require_once(__DIR__.'/isdk.php');
class Infusionsoft extends \iSDK {
protected $_app;
/**
* Init the sdk
*
*/
public function __construct( $connectionName )
{
$this->_app = parent::cfgCon($connectionName);
}
public function test()
{
dd('works');
}
}
I set up the service provider and the facade alias of Infusionsoft in app/config/config.php.
When I try running methods belonging to the extended iSDK class against an instance of Spoolphiz\Infusionsoft\Facade\Infusionsoft I get undefined method errors, such as the following:
Call to undefined method Spoolphiz\Infusionsoft\Facades\Infusionsoft::loadCon()
Why is this? The whole point of facades is to be able to call methods against its root class...
Looks like I was being stupid. I was developing this package in the laravel workbench. Once it was done I submitted it to packagist and set up a requirement for it in the same laravel app. Having the package installed in vendors directory and in the workbench caused some sort of conflict.
Lesson learned: make sure you dont have the same package in your workbench and in your application's vendors directory.

Resources