I recently started a project in Symfony2 and I need to run some methods before and after every action to avoid code redundancy (like preDispatch/postDispatch from Zend Framework and PreExecute/PostExecute from Symfony1).
I created a base class from which all the controllers are inherited,
and registered an event listener to run controller's preExecute() method before running requested action, but after reading tons of documentation and questions from here I still can't find how to run postExecute().
Foo/BarBundle/Controller/BaseController.php:
class BaseController extends Controller {
protected $_user;
protected $_em;
public function preExecute() {
$user = $this->get('security.context')->getToken()->getUser();
$this->_user = $user instanceof User ? $user : null;
$this->_em = $this->getDoctrine()->getEntityManager();
}
public function postExecute() {
$this->_em->flush();
}
}
Foo/BarBundle/Controller/FooController.php:
class FooController extends BaseController {
public function indexAction() {
$this->_user->setName('Eric');
$this->_em->persist($this->_user);
}
}
Foo/BarBundle/EventListener/PreExecute.php:
class PreExecute {
public function onKernelController(FilterControllerEvent $event) {
if (HttpKernelInterface::MASTER_REQUEST === $event->getRequestType()) {
$controllers = $event->getController();
if (is_array($controllers)) {
$controller = $controllers[0];
if (is_object($controller) && method_exists($controller, 'preExecute')) {
$controller->preExecute();
}
}
}
}
}
There is a discussion of this here and this particular example by schmittjoh may lead you in the right direction.
<?php
class Listener
{
public function onKernelController($event)
{
$currentController = $event->getController();
$newController = function() use ($currentController) {
// pre-execute
$rs = call_user_func_array($currentController, func_get_args());
// post-execute
return $rs;
};
$event->setController($newController);
}
}
Related
I am implementing the Repository Pattern (service) in a Laravel application and I have some doubts about the usage of interfaces with these services.
I have created an interface called CRUD (code bellow) to serve as a way to always keep the same names for the services that are going to implement CRUD methods.
<?php
namespace App\Interfaces;
interface CRUD
{
public function create(array $data);
public function update(int $id, array $data);
public function delete(string $ids);
};
Bellow there's an example of how I call my service and the service itself, and that's where my doubts are. Usually I'll see people witing an interface for each service and demanding the controller to have injected an objet of that type. Because of that, people will have to bind a specific type (interface) to the controller. It seems redundant and thus I simply passed the service I need.
Now, is this ok or I should pass the CRUD interface to the controller in this case? Or should I even create another interface specifically for each service?
<?php
namespace App\Http\Controllers\Cms;
use App\Http\Controllers\Controller;
use App\Http\Requests\GroupRequest;
use App\Models\Group;
use App\Services\GroupsService;
use Illuminate\Http\Request;
class GroupsController extends Controller
{
private $service;
public function __construct(GroupsService $service)
{
$this->service = $service;
}
public function store(GroupRequest $request)
{
$result = $this->service->create($request->all());
return redirect()->back()->with('response', $result);
}
public function update(GroupRequest $request, $id)
{
$result = $this->service->update($id, $request->all());
return redirect()->back()->with('response', $result);
}
public function destroy($groups_id)
{
$result = $this->service->delete($groups_id);
return redirect()->back()->with('response', $result);
}
}
<?php
namespace App\Services;
use App\Models\Group;
use App\Interfaces\CRUD;
use Exception;
class GroupsService implements CRUD
{
public function listAll()
{
return Group::all();
}
public function create(array $data)
{
$modules_id = array_pop($data);
$group = Group::create($data);
$group->modules()->attach($modules_id);
return cms_response(trans('cms.groups.success_create'));
}
public function update(int $id, array $data)
{
try {
$modules_ids = $data['modules'];
unset($data['modules']);
$group = $this->__findOrFail($id);
$group->update($data);
$group->modules()->sync($modules_ids);
return cms_response(trans('cms.groups.success_update'));
} catch (\Throwable $th) {
return cms_response($th->getMessage(), false, 400);
}
}
public function delete(string $ids)
{
Group::whereIn('id', json_decode($ids))->delete();
return cms_response(trans('cms.groups.success_delete'));
}
private function __findOrFail(int $id)
{
$group = Group::find($id);
if ($group instanceof Group) {
return $group;
}
throw new Exception(trans('cms.groups.error_not_found'));
}
}
If you want to use Repository Design Patteren You have to create seprate Interface for each service accroing to SOLID Principle. You have to create custom service provider and register your interface and service class and then inject interface in construtor of controller.
You can also follow below article.
https://itnext.io/repository-design-pattern-done-right-in-laravel-d177b5fa75d4
I did something with repo pattern in laravel 8 you might be interested:
thats how i did it:
first of all, you need to implement a provider
in this file i created the binding:
App\ProvidersRepositoryServiceProvider.php
use App\Interfaces\EventStreamRepositoryInterface;
use App\Repositories\EventStreamRepository;
use Illuminate\Support\ServiceProvider;
class RepositoryServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind(EventStreamRepositoryInterface::class, EventStreamRepository::class);
}
}
then in file:
app\Interfaces\EventStreamRepositoryInterface.php
interface EventStreamRepositoryInterface {
public function index();
public function create( Request $request );
public function delete($id);
}
in file:
App\Repositories\EventStreamRepository.php
class EventStreamRepository implements EventStreamRepositoryInterface{
public function index()
{
return EventStream::with(['sessions'])
->where([ ["status", "=", 1] ] )
->orderBy('created_at', 'DESC')
->get();
}
public function create(Request $request)
{
request()->validate([
"data1" => "required",
"data2" => "required"
]);
$EventStream = EventStream::create([
'data1' => request("data1"),
'data2' => request('data2')
]);
return $EventStream->id;
}
public function delete($id)
{
return EventStream::where('id', $id)->delete();
}
}
in file:
App\Http\Controllers\EventStreamController.php
use App\Interfaces\EventStreamRepositoryInterface;
class EventStreamController extends Controller{
private EventStreamRepositoryInterface $eventStreamRepository;
public function __construct(EventStreamRepositoryInterface $eventStreamRepository)
{
$this->eventStreamRepository = $eventStreamRepository;
}
public function index():JsonResponse
{
$this->eventStreamRepository->index();
}
public function store(Request $request ):JsonResponse
{
$this->eventStreamRepository->create($request);
}
public function destroy($id):JsonResponse
{
$this->eventStreamRepository->delete($id);
}
}//class
note: i think i removed all unnecessary -validations- and -returns- in controller for better reading.
Hope it helps!!
I have a simple service on PHP with this structure:
Controller
ChannelControllerAPI.php
Service
ChannelService.php
Model
Repository
ChannelRepositoryInterface.php
RedisChannelRepository.php // implements ChannelRepositoryInterface
SqlChannelRepository.php // implements ChannelRepositoryInterface
HybridChannelRepository.php // implements ChannelRepositoryInterface
AbstractChannel.php
BusinessChannel.php // extends AbstractChannel
UserChannel.php // extends AbstractChannel
And I use DI container to resolve dependencies.
API controller code:
class ChannelControllerApi extends Controller
{
private $channelService;
public function __construct(ChannelService $channelService)
{
$this->channelService = $channelService;
}
public function getChannelInfo()
{
$channelId = $ths->request->Post('channelId');
$channelType = $ths->request->Post('channelType');
$result = $channelService->getChannelInfo($channelId, $channelType);
// return API request
}
}
I get ChannelService in Controller Api __construct with DI;
ChannelService code:
class ChannelService
{
private $channelRepository;
public function __construct(ChannelRepository $channelRepository)
{
$this->channelRepository = $channelRepository;
}
public function getChannelInfo(int $channelId, string $channelType)
{
return $channelRepository ->findRules($channelId, $channelType);
}
}
ChannelRepository code for example SQLRepository:
use channel/Models/AbstractChannel;
class SqlChannelRepository
{
/**
* Channel $model (ActiveRecord or DTO Model)
*/
private $model;
public function __construct(AbstractChannel $model)
{
$this->model= $model;
}
public function find(int $channelId, string $channelType) : Channel
{
return $this->model->where('id', $channelId)->where('type', $channelType)->getModel();
}
}
Abstract channel model class code:
abstract class Channel extends ActiveRecordModel
{
protected $tableName = 'channel_business';
protected $fildmap = [
'id',
'type',
// ...
];
}
BusinessChannel model class code:
class Channel extends AbstractChannel;
{
protected $tableName = 'channel_business';
}
UserChannel model class code:
class UserChannel extends AbstractChannel;
{
protected $tableName = 'channel_user';
}
I don't want to pass $channelId and $type to Service or to Repository.
How? and in which layer can I pass the right model object to SqlChannelRepository?
Where should this logic be placed:
if ($requset->Post('type') == 'user') {
$model = new UserChannel();
// or setting in DI container: app()->container::set('UserChannel');
} elseif ($requset->Post('type') == 'business') {
$model = new BusinessChannel();
} elseif .... {
......
}
Please help, which pattern or architect solution (can I use to create right ChannelModel by request ($_POST) params and pass it to my repository)?
Sorry for my poor English:)
I'm building application in Phalcon PHP where I have database with access from website and API.
In normal website I would create MVC like here:
- app
-- controllers
-- models
-- views
- public
but I have problem with duplicate code for API and Web.
Sample code:
class Users extends Model {
// ...
protected $id;
protected $username;
protected $email;
// setters and getters, validation
}
class UserController extends ControllerBase {
// ...
public function loginAction() {
if ($this->request->isPost()) {
// ... get post
// check login is correct
// create session
// redirect
}
$this->view->var = $var;
}
}
class ApiController extends ControllerBase {
// ...
public function loginAction() {
if ($this->request->isPost() //or put) {
$json = $this->request->getJsonRawBody();
// ... get json
// check login is correct
// create session
}
$response->setStatusCode('2xx/4xx', 'msg');
$response->setJsonContent([
'status' => 'OK / ERROR',
'message' => '$msg / $ex->getMessage()'
]);
}
}
Now I would create class with logic for check is user data correct.
I think about class like this:
class MyClass extends ParentClass {
public function login($username, $password) {
$user = Users::findFirstByEmail($email);
if ($user->password === hash($password)) {
$successLogin = new UserSuccessLogins();
$successLogin ->setId('id');
$successLogin ->setIpAddress('ip');
$successLogin ->save();
} else {
$failedLogin = new UserFailedLogins();
$failedLogin->setId('id');
$failedLogin->setIpAddress('ip');
$failedLogin->save();
}
}
}
And now I could use it in controllers like here:
class UserController extends ControllerBase {
public function loginAction() {
if ($this->request->isPost()) {
$c = new MyClass();
if ($c->login($username, $password)) {
// redirect
}
}
$this->view->var = $var;
}
}
class ApiController extends ControllerBase {
public function loginAction() {
if ($this->request->isPost() //or put) {
$c = new MyClass();
if ($c->login($username, $password)) {
// send json OK
} else {
// send json Error
}
}
}
}
What is best way for this? I don't want logic in model class.
I have read about Plugin and Component, but I don't know how create good self commented code.
You might be looking for Phalcon Multimodule, have a look at this example. Besides "Front-End" and "Back-End" modules, you can add "API" module.
OK, I'm going to extend my project with components like here:
-app
--components
--controllers
--models
--views
-public
Now, my code may looks like below:
use Phalcon\Mvc\Model;
class Users extends Model {
// ...
protected $id;
protected $username;
protected $email;
// setters and getters, validation
}
use Phalcon\Mvc\User\Component;
class UserComponent extends Component {
// class with access to dependecy injector
public login ($email, $password) {
$user = Users::findFirstByEmail($email);
// logic with setting session in $di
}
}
class UserController extends ControllerBase {
public function loginAction() {
if ($this->request->isPost()) {
$userComponent = new UserComponent();
if ($userComponent ->login($username, $password)) {
return $this->response->redirect($this->url->getBaseUri(), false, 301);
} else {
$this->flash->error('message');
}
}
// setting view variables if not post or login filed
$this->view->var = $var;
}
}
class ApiController extends ControllerBase {
public function loginAction() {
if ($this->request->isPost()) {
$userComponent = new UserComponent();
if ($userComponent ->login($username, $password)) {
//json OK
} else {
//json Error
}
}
}
}
If no one have better proposition I'll close this topic as solved in few days.
Your suggestion is a good option, however if you want to decouple and segregate responsibilities in a better way, you can try to use a service layer like in this example https://github.com/phalcon/mvc/tree/master/multiple-service-layer-model. Where you will have:
entities ( the models generated by phalcon)
repositories ( all the operations that requires fetching, updating or persisting data)
services (where the business logic is).
Whit this the call graph can be summarised as follow:
controllers -> services -> repositories -> entities
Note that the dependencies go in a single direction, nonetheless for simple tasks you can use a repo inside the controller directly o a entity inside the service, is up to you how hard or flexible your architecture will be.
I hope It is clear regards.
I'm working on a Laravel package it is working when I use it in my Laravel project, but when I want to test it with Orchestra Testbench I always get current route null inside middlware.
Test directory on Github: https://github.com/yoeunes/larafast/tree/master/tests
Base TestCase:
class TestCase extends Orchestra\Testbench\TestCase
{
protected function getEnvironmentSetUp($app)
{
$kernel = app('Illuminate\Contracts\Http\Kernel');
$kernel->pushMiddleware(\Illuminate\Session\Middleware\StartSession::class);
$kernel->pushMiddleware(\Yoeunes\Larafast\Middlewares\BlacklistRoutes::class);
}
}
WebControllerTest:
class WebControllerTest extends TestCase
{
public function setUp()
{
parent::setUp();
/** #var \Illuminate\Routing\Router $router */
$router = $this->app['router'];
$router->resource('lessons', 'Yoeunes\Larafast\Tests\Stubs\Controllers\Web\LessonController');
}
/** #test */
public function it_show_create_page()
{
/** #var TestResponse $response */
$response = $this->call('get', '/lessons/create');
dd($response);
$response->assertSuccessful();
$response->assertSee('<title>lessons create | Larafast</title>');
$response->assertSee('<i class="fa fa-plus-circle"></i> lessons create');
$response->assertSee('<form method="POST" action="http://localhost/lessons" accept-charset="UTF-8" enctype="multipart/form-data">');
}
}
BlacklistRoutes Middleware:
class BlacklistRoutes
{
public function handle($request, Closure $next)
{
dd(app('router')->getCurrentRoute()); // always get null
if (null !== ($route = app('router')->getCurrentRoute())
&& is_a($controller = $route->getController(), Controller::class)
&& in_array($route->getActionMethod(), $controller->getBlacklist(), true)) {
throw new BlacklistRouteException();
}
return $next($request);
}
}
According to Orchestra Testbench Github repository :
AFAIK global middleware is resolved before route is resolved,
therefore getCurrentRoute() shouldn't be available yet.
So the resolve my problem I can access to current route from within the controller constructor like this:
public function __construct()
{
$this->middleware(function ($request, Closure $next) {
if (null !== ($route = app('router')->getCurrentRoute())
&& is_a($controller = $route->getController(), Controller::class)
&& in_array($route->getActionMethod(), $controller->getBlacklist(), true)) {
throw new BlacklistRouteException();
}
});
}
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);
}
}