Laravel Extend Vendor Class - php

I tried extending an Illuminate Class Translator
I created a class and extended to translator
then I added this line to my RepositoryServiceProvider
$this->app->bind(\Illuminate\Translation\Translator::class, \App\Repositories\Translation\TranslationTranslator::class);
But its not working
what am I doing wrong?
the class as follows
<?php
namespace App\Repositories\Translation;
use Countable;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Support\Collection;
use Illuminate\Support\Traits\Macroable;
use Illuminate\Support\NamespacedItemResolver;
use Symfony\Component\Translation\MessageSelector;
use Symfony\Component\Translation\TranslatorInterface;
class TranslationTranslator extends \Illuminate\Translation\Translator
{
/**
* Parse a key into namespace, group, and item.
*
* #param string $key
* #return array
*/
public function parseKey($key)
{
\Log::info('got in');
die;
$segments = parent::parseKey($key);
if (is_null($segments[0])) {
$segments[0] = #explode('.', $segments[2])[0];
}
if ($segments[1] == 'translatable') {
$segments[1] = #explode('.', $segments[2])[0] . '_' . #explode('.', $segments[2])[1];
}
return $segments;
}
}
UPDATE
Apparently the Translator class has a constructor
public function __construct(LoaderInterface $loader, $locale)
{
$this->loader = $loader;
$this->locale = $locale;
}
so my binding has to pass by the interface.. which cannot be instantiated
public function boot()
{
$app = $this->app;
$this->app->bind(\Illuminate\Translation\Translator::class, function() use ($app){
return $app->make(\App\Repositories\Translation\TranslationTranslator::class);
});
}
and getting this error
Illuminate\Contracts\Container\BindingResolutionException with message
'Target [Illuminate\Translation\LoaderInterface] is not instantiable
while building [App\Repositories\Translation\TranslationTranslator].'

You can use closure to resolve the classes
$this->app->bind(\Illuminate\Translation\Translator::class, function(){
return new \App\Repositories\Translation\TranslationTranslator;
});
Secondly translator is binded with laravel in using translator alias.
You can also override it.
$this->app->bind('translator', function(){
return new \App\Repositories\Translation\TranslationTranslator;
})

This worked for me
$app = $this->app;
$loader = $app['translation.loader'];
$locale = $app['config']['app.locale'];
$this->app->bind('translator', function() use ($loader, $locale){
return new \App\Repositories\Translation\TranslationTranslator($loader, $locale);
});
I hope this help you

Check below example
namespace App\Repositories\Translation;
use Illuminate\Translation\Translator;
class TranslationTranslator extends Translator
{
public function get()
{
....
}
}
No need to anything. you only need to add new functions or override base class functions. Then you can use this class as simple other classes.

Try changing it to this:
$this->app->instance(\Illuminate\Translation\Translator::class, \App\Repositories\Translation\TranslationTranslator::class);
That should then change the instance of the interface.
UPDATE
if you are just trying to add a new method, the Translator class is Macroable. So you can do the following
Translator::macro('parseKey', function ($key) {
\Log::info('got in');
die;
$segments = parent::parseKey($key);
if (is_null($segments[0])) {
$segments[0] = #explode('.', $segments[2])[0];
}
if ($segments[1] == 'translatable') {
$segments[1] = #explode('.', $segments[2])[0] . '_' . #explode('.', $segments[2])[1];
}
return $segments;
});
You would then be able to call your method as you normally would. For example:
app(Translator::class)->parseKey($key);

Related

How do you stub a method to return an array

I have DocumentService class like this:
namespace App\Services;
use App\Api\StorageServiceInterface;
class DocumentService
{
private $storageService;
function __construct(StorageServiceInterface $storageService)
{
$this->storageService = $storageService;
}
public function createFolder(User $user)
{
$folderData = $this->storageService->createFolder(
'/Users/' . $user->email
);
$user->folder = $folderData['folder_id'];
$user->save();
return $user->folder;
}
}
Partial implmentation of the LocalStorageService.
namespace App\Api;
class LocalStorageService implements StorageServiceInterface
{
public function createFolder($folder)
{
...
return ['folder_id' => $folder_id, 'folder_path' => $folder_path];
}
}
I'm testing the DocumentService class. I'm trying to mock the createFolder() method on the LocalStorageService which implements StorageServiceInterface.
How do I configure this stub to return the array with PHPUnit?
I've tried this: (partial code from my test)
namespace Tests\Unit\Services;
use App\User;
use App\Api\LocalStorageService;
public function testCreateFolder()
{
$user = factory(User::class)->make();
$localStorageService = $this->createMock(LocalStorageService::class);
$localStorageService->method('createFolder')
->willReturn(array('folder_id' => 'random_id', 'folder_path' => ("/Users/" . $user->email)));
}
But I'm only getting random_id.
Although it should work with classes as well as interfaces, interfaces should be preferred as this is what client of the interface expects.
This following should work:
public function testCreateFolder()
{
$user = factory(User::class)->make();
$folderPath = '/Users/' . $user->email;
$localStorageService = $this->createMock(StorageServiceInterface::class);
$localStorageService
->expects($this->once())
->method('createFolder')
->with($folderPath)
->willReturn(
['folder_id' => 'random_id', 'folder_path' => $folderPath]
);
$documentService = new DocumentService($localStorageService);
$documentService->createFolder($user);
}
By adding the with() call you're making sure only if the path is passed it returns the given array.

Dependency injection not working with League\Route and League\Container

I'm building a web application right now and I'm facing a problem with my controller.
I want to send to my controller my League\Plate\Engine (registred in my Container) but I keep having the same error : Argument 3 passed to App\Controller\Main::index() must be an instance of League\Plates\Engine, array given
Here is my files :
dependencies.php
use League\Container\Container;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use Yajra\Pdo\Oci8;
use League\Container\ReflectionContainer;
$container = new Container();
// Active auto-wiring
$container->delegate(
new ReflectionContainer
);
// Others dependencies
// ...
// Views
$container->add('view', function () {
$templates = new League\Plates\Engine();
$templates->addFolder('web', __DIR__ . '/templates/views/');
$templates->addFolder('emails', __DIR__ . '/templates/emails/');
// Extension
//$templates->loadExtension(new League\Plates\Extension\Asset('/path/to/public'));
//$templates->loadExtension(new League\Plates\Extension\URI($_SERVER['PATH_INFO']));
return $templates;
});
return $container;
routes.php
use League\Route\RouteCollection;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
$route = new RouteCollection($container);
// Page index
$route->get('/', 'App\Controller\Main::index');
// Others routes...
return $route;
Main.php
namespace App\Controller;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use League\Plates\Engine;
class Main
{
public function index(ServerRequestInterface $request, ResponseInterface $response, Engine $templates) {
//return $response->getBody()->write($this->templates->render('web::home'));
return $response;
}
}
Thank you in advance
EDIT
I've made a progress.
I extended the Main class to extends the abstract class BaseController which looks like this :
namespace App\Controller;
use League\Plates\Engine;
class BaseController
{
protected $templates;
public function __construct(Engine $templates) {
$this->templates = $templates;
}
}
The first error goes away, but another one show up. In the Main class, I would like to use the view object that I instanciate in the container, but the object passed to the constructor is an empty one :
Main.php
class Main extends BaseController
{
public function index(ServerRequestInterface $request, ResponseInterface $response) {
echo '<pre>'.print_r($this->templates,1).'</pre>'; // Return an empty Plate Engine object
return $response->getBody()->write($this->templates->render('web::home'));
//return $response;
}
}
And this doesn't explain why the first error shows up
EDIT 2
After some digging, I finally make it works, but I sense that something is wrong.
I replaced in the container the term view by the namespace of the Engine class :
$container->add('League\Plates\Engine', function () {
// The same as before
});
In Main.php I've updated the index function like this :
public function index(ServerRequestInterface $request, ResponseInterface $response) {
$body = $response->getBody();
$body->write($this->templates->render('web::home'));
return $response->withBody($body);
}
And the page doesn't throw a 500 error and the html file is displayed correctly.
But, what if I want to change the template engine by Twig for example ? This would mean I'll need to change all the call to $container->get('League\Plate\Engine'); by $container->get('What\Ever'); ? That's not very practical!
I probably missed something!
And the problem will rise again when I'll want to use my PDO object... or every other object.
Ok so I solved my problem by registering my Controllers classes in the container itself.
For example, for displaying the index page, the Main class call the index function. In my container, I call
$container->add('App\Controller\Main')
->withArgument($container->get('view'));
To summary :
bootstap.php (called by index.php)
require __DIR__ . '/../../vendor/autoload.php';
$dotenv = new \Dotenv\Dotenv(__DIR__ . '/../');
$dotenv->load();
$config = new Config(__DIR__ . '/../config/');
$container = require __DIR__ . '/../dependencies.php';
$route = require __DIR__ . '/../routes.php';
$response = $route->dispatch($container->get('request'), $container->get('response'));
$container->get('emitter')->emit($response);
dependencies.php
$container = new Container();
// activate auto-wiring
$container->delegate(
new ReflectionContainer
);
// Others dependencies...
// Views
$container->add('view', function () {
$templates = new League\Plates\Engine();
$templates->addFolder('web', __DIR__ . '/templates/views/');
$templates->addFolder('emails', __DIR__ . '/templates/emails/');
// Extension
//$templates->loadExtension(new League\Plates\Extension\Asset('/path/to/public'));
//$templates->loadExtension(new League\Plates\Extension\URI($_SERVER['PATH_INFO']));
return $templates;
});
// THIS IS THE TRICK
$container->add('App\Controller\Main')
->withArgument($container->get('view'));
// others controllers...
return $container;
routes.php
$route = new RouteCollection($container);
// Page index
$route->get('/', 'App\Controller\Main::index');
// Others routes...
return $route;
I have no idea why or how this work !
This works, because you are registering with the container your controller class, and telling the container to dependency inject your View class in the constructor..
The line you added, here, is doing that:
$container->add('App\Controller\Main')
->withArgument($container->get('view'));
The Hello World documentation example explains it perfectly.
http://container.thephpleague.com/3.x/constructor-injection/

Laravel 5 return JSON or View depends if ajax or not

I would like to know if there is a magic method to use this scenario :
If I call a page via an AJAX request the controller returns a JSON object, otherwise it returns a view, i'm trying to do this on all my controllers without changin each method.
for example i know that i can do this :
if (Request::ajax()) return compact($object1, $object2);
else return view('template', compact($object, $object2));
but I have a lot of controllers/methods, and I prefer to change the basic behavior instead of spending my time to change all of them. any Idea ?
The easiest way would be to make a method that is shared between all of your controllers.
Example:
This is your controller class that all other controllers extend:
<?php namespace App\Http\Controllers;
use Illuminate\Routing\Controller as BaseController;
abstract class Controller extends BaseController
{
protected function makeResponse($template, $objects = [])
{
if (\Request::ajax()) {
return json_encode($objects);
}
return view($template, $objects);
}
}
And this is one of the controllers extending it:
<?php namespace App\Http\Controllers;
class MyController extends Controller
{
public function index()
{
$object = new Object1;
$object2 = new Object2;
return $this->makeResponse($template, compact($object, $object2));
}
}
Update for Laravel 5+
<?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;
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
protected function makeResponse($request, $template, $data = [])
{
if ($request->ajax()) {
return response()->json($data);
}
return view($template, $data);
}
}
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class MyController extends Controller
{
public function index(Request $request)
{
$object = new Object1;
$object2 = new Object2;
return $this->makeResponse($request, $template, compact($object, $object2));
}
}
There is no magic but you can easily override ViewService in 3 steps:
1.create your view factory (your_project_path/app/MyViewFactory.php)
<?php
/**
* Created by PhpStorm.
* User: panos
* Date: 5/2/15
* Time: 1:35 AM
*/
namespace App;
use Illuminate\View\Factory;
class MyViewFactory extends Factory {
public function make($view, $data = array(), $mergeData = array())
{
if (\Request::ajax()) {
return $data;
}
return parent::make($view, $data, $mergeData);
}
}
2.create your view service provider (your_project_path/app/providers/MyViewProvider.php)
<?php namespace App\Providers;
use App\MyViewFactory;
use Illuminate\View\ViewServiceProvider;
class MyViewProvider extends ViewServiceProvider {
/**
* Register the application services.
*
* #return void
*/
public function register()
{
parent::register();
}
/**
* Overwrite original so we can register MyViewFactory
*
* #return void
*/
public function registerFactory()
{
$this->app->singleton('view', function($app)
{
// Next we need to grab the engine resolver instance that will be used by the
// environment. The resolver will be used by an environment to get each of
// the various engine implementations such as plain PHP or Blade engine.
$resolver = $app['view.engine.resolver'];
$finder = $app['view.finder'];
// IMPORTANT in next line you should use your ViewFactory
$env = new MyViewFactory($resolver, $finder, $app['events']);
// We will also set the container instance on this view environment since the
// view composers may be classes registered in the container, which allows
// for great testable, flexible composers for the application developer.
$env->setContainer($app);
$env->share('app', $app);
return $env;
});
}
}
3.in your_project_path/config/app.php:
change 'Illuminate\View\ViewServiceProvider',
to 'App\Providers\MyViewProvider',
What this do:
it tells your application to use another view provider which will register your view factory
$env = new MyViewFactory($resolver, $finder, $app['events']);
in line 33 of MyViewProvider.php which will check if request is AJAX and return if true or continue with original behavior
return parent::make($view, $data, $mergeData);
in MyViewFactory.php line 19
Hope this help you,
In laravel 5.1, this is the best way:
if (\Illuminate\Support\Facades\Request::ajax())
return response()->json(compact($object1, $object2));
else
return view('template', compact($object, $object2));
The solution suggested by #ryanwinchester is really good. I, however, wanted to use it for the responses from update() and delete(), and there naturally return view() at the end doesn't make a lot of sense as you mostly want to use return redirect()->route('whatever.your.route.is'). I thus came up with that idea:
// App\Controller.php
/**
* Checks whether request is ajax or not and returns accordingly
*
* #param array $data
* #return mixed
*/
protected function forAjax($data = [])
{
if (request()->ajax()) {
return response()->json($data);
}
return false;
}
// any other controller, e.g. PostController.php
public function destroy(Post $post)
{
// all stuff that you need until delete, e.g. permission check
$comment->delete();
$r = ['success' => 'Wohoo! You deleted that post!']; // if necessary
// checks whether AJAX response is required and if not returns a redirect
return $this->forAjax($r) ?: redirect()->route('...')->with($r);
}

Laravel 5 Implement multiple Auth drivers

Synopsis
I am building a system with at least two levels of Authentication and both have separate User models and tables in the database. A quick search on google and the only solution thus far is with a MultiAuth package that shoehorns multiple drivers on Auth.
My goal
I am attempting to remove Auth which is fairly straight-forward. But I would like CustomerAuth and AdminAuth using a separate config file as per config/customerauth.php and config\adminauth.php
Solution
I'm assuming you have a package available to work on. My vendor namespace in this example will simply be: Example - all code snippets can be found following the instructions.
I copied config/auth.php to config/customerauth.php and amended the settings accordingly.
I edited the config/app.php and replaced the Illuminate\Auth\AuthServiceProvider with Example\Auth\CustomerAuthServiceProvider.
I edited the config/app.php and replaced the Auth alias with:
'CustomerAuth' => 'Example\Support\Facades\CustomerAuth',
I then implemented the code within the package for example vendor/example/src/. I started with the ServiceProvider: Example/Auth/CustomerAuthServiceProvider.php
<?php namespace Example\Auth;
use Illuminate\Auth\AuthServiceProvider;
use Example\Auth\CustomerAuthManager;
use Example\Auth\SiteGuard;
class CustomerAuthServiceProvider extends AuthServiceProvider
{
public function register()
{
$this->app->alias('customerauth', 'Example\Auth\CustomerAuthManager');
$this->app->alias('customerauth.driver', 'Example\Auth\SiteGuard');
$this->app->alias('customerauth.driver', 'Example\Contracts\Auth\SiteGuard');
parent::register();
}
protected function registerAuthenticator()
{
$this->app->singleton('customerauth', function ($app) {
$app['customerauth.loaded'] = true;
return new CustomerAuthManager($app);
});
$this->app->singleton('customerauth.driver', function ($app) {
return $app['customerauth']->driver();
});
}
protected function registerUserResolver()
{
$this->app->bind('Illuminate\Contracts\Auth\Authenticatable', function ($app) {
return $app['customerauth']->user();
});
}
protected function registerRequestRebindHandler()
{
$this->app->rebinding('request', function ($app, $request) {
$request->setUserResolver(function() use ($app) {
return $app['customerauth']->user();
});
});
}
}
Then I implemented: Example/Auth/CustomerAuthManager.php
<?php namespace Example\Auth;
use Illuminate\Auth\AuthManager;
use Illuminate\Auth\EloquentUserProvider;
use Example\Auth\SiteGuard as Guard;
class CustomerAuthManager extends AuthManager
{
protected function callCustomCreator($driver)
{
$custom = parent::callCustomCreator($driver);
if ($custom instanceof Guard) return $custom;
return new Guard($custom, $this->app['session.store']);
}
public function createDatabaseDriver()
{
$provider = $this->createDatabaseProvider();
return new Guard($provider, $this->app['session.store']);
}
protected function createDatabaseProvider()
{
$connection = $this->app['db']->connection();
$table = $this->app['config']['customerauth.table'];
return new DatabaseUserProvider($connection, $this->app['hash'], $table);
}
public function createEloquentDriver()
{
$provider = $this->createEloquentProvider();
return new Guard($provider, $this->app['session.store']);
}
protected function createEloquentProvider()
{
$model = $this->app['config']['customerauth.model'];
return new EloquentUserProvider($this->app['hash'], $model);
}
public function getDefaultDriver()
{
return $this->app['config']['customerauth.driver'];
}
public function setDefaultDriver($name)
{
$this->app['config']['customerauth.driver'] = $name;
}
}
I then implemented Example/Auth/SiteGuard.php (note the methods implemented have an additional site_ defined, this should be different for other Auth drivers):
<?php namespace Example\Auth;
use Illuminate\Auth\Guard;
class SiteGuard extends Guard
{
public function getName()
{
return 'login_site_'.md5(get_class($this));
}
public function getRecallerName()
{
return 'remember_site_'.md5(get_class($this));
}
}
I then implemented Example/Contracts/Auth/SiteGuard.php
use Illuminate\Contracts\Auth\Guard;
interface SiteGuard extends Guard {}
Finally I implemented the Facade; Example/Support/Facades/Auth/CustomerAuth.php
<?php namespace Example\Support\Facades;
class CustomerAuth extends Facade
{
protected static function getFacadeAccessor()
{
return 'customerauth';
}
}
A quick update, when trying to use these custom auth drivers with phpunit you may get the following error:
Driver [CustomerAuth] not supported.
You also need to implement this, the easiest solution is override the be method and also creating a trait similar to it:
<?php namespace Example\Vendor\Testing;
use Illuminate\Contracts\Auth\Authenticatable as UserContract;
trait ApplicationTrait
{
public function be(UserContract $user, $driver = null)
{
$this->app['customerauth']->driver($driver)->setUser($user);
}
}

Unable to pass class instance to constructor

I have installed this package https://github.com/Intervention/image with composer. I have added
'IntImage' => 'Intervention\Image\Facades\Image'
to config/app under aliases
I get the following error and cant figure out what I am doing incorrectly I am sure it has something to do with namespacing /autoloading but app/acme is in the psr-o section of composer.json
'Argument 1 passed to
Acme\Services\Images\InterventionImageEditor::__construct() must be an
instance of IntImage, none given, called in
/var/www/app/ACme/Providers/ImageEditorServiceProvider.php on line 14
and defined' in
/var/www/app/Acme/Services/Images/InterventionImageEditor.php:11
I have the following directory structure
app
acme
Providers
ImageEditorServiceProvider.php
Services
Images
ImageEditorInterface.php
InterventionImageEditor.php
and the content of the files
ImageEditorServiceProvider.php
<?php namespace Acme\Providers;
use Illuminate\Support\ServiceProvider;
use Acme\Services\Images\InterventionImageEditor;
/**
*
*/
class ImageEditorServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind('Acme\Services\Images\ImageEditorInterface', function () {
return new InterventionImageEditor();
});
}
}
ImageEditorInterface.php
<?php namespace Acme\Services\Images;
interface ImageEditorInterface
{
public function hello();
}
InterventionImageEditor.php
<?php namespace Acme\Services\Images;
use IntImage;
/**
*
*/
class InterventionImageEditor implements ImageEditorInterface
{
protected $imageeditor;
public function __construct(IntImage $imageeditor)
{
$this->imageeditor = $imageeditor;
}
public function hello()
{
$hello = 'hello';
return $hello;
}
}
Can I
Use IntImage;
in this way because it is a facade or am I missing something?
edit to include solution;
changing the service provider to the following resolved the problem
<?php namespace Acme\Providers;
use Illuminate\Support\ServiceProvider;
use Acme\Services\Images\InterventionImageEditor;
use IntImage;
/**
*
*/
class ImageEditorServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind('Acme\Services\Images\ImageEditorInterface', function () {
$intimage = new IntImage;
return new InterventionImageEditor($intimage);
});
}
}
The error is coming from ImageEditorServiceProder.php:
$this->app->bind('Acme\Services\Images\ImageEditorInterface', function () {
return new InterventionImageEditor();
});
Here you are instantiating the InterventionImageEditor without any parameters. You InterventionImageEditor requires one parameter of type IntImage.
If there are places where you won't have IntImage when instantiating InterventionImageEditor then you need to update your InterventionImageEditor::__construct so that it accepts null(possibly).
function __construct(IntImage $imageeditor = null)
{
if (is_null($imageeditor)) {
// Construct a default imageeditor
// $imageeditor = new ...;
}
$this->imageeditor = $imageeditor;
}
i am not sure you can using IntImage because this file is Facades.
if you want to extending the intervention class. you should add Intervention\Image\Image to your ImageEditorServiceProvider.
use Intervention\Image\Image;
class ImageEditorServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind('Acme\Services\Images\ImageEditorInterface', function () {
return new InterventionImageEditor(new Image);
});
}
}

Categories