I got a problem using SOAP client in Laravel. I want to send salesorders/customers from an shop to an ERP system. The Structure looks like this: In routes->web.php I call my Controller
Route::get('/show_orders', 'SalesOrdersController#store');
I created an controller SalesOrderController and it looks like this
<?php
namespace App\Http\Controllers;
class SalesOrdersController extends Controller
{
private $soapClient;
public function __construct()
{
$this->middleware('auth');
$this->soapClient = app('soap_client_sales_order');
}
public function store()
{
$soapClient->__setLocation(env('BYD_DOMAIN') . $serviceUrl);
$parameters['BasicMessageHeader'] = array(
'ID' => '00000000000102dcade9bcb0aa000c68',
);
$parameters['Customer'] = array(
'CategoryCode' => '1',
'CustomerIndicator' => 'true',
'Person' => array(
'GivenName' => 'Frank',
'FamilyName' => 'Sent',
),
);
$soapResult = $this->soapClient->MaintainBundle_V1($parameters);
}
}
I created an ServiceProvider SoapServiceProvider – looks like this:
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class SoapServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot()
{
//
}
/**
* Register the application services.
*
* #return void
*/
public function register()
{
$this->app->singleton('soap_client_sales_order', function () {
$serviceUrl = '/sap/bc/srt/scs/sap/managecustomerin1';
$wsdlPath = 'soap/managecustomerin1.wsdl';
$soapClient = new \SoapClient(
storage_path($wsdlPath),
array(
'trace' => 1,
'soap_version' => SOAP_1_2,
'exceptions' => 1,
'login' => env('SOAP_USER'),
'password' => env('SOAP_PASSWORD'),
)
);
});
$soapClient->__setLocation(env('BYD_DOMAIN') . $serviceUrl);
return $soapClient;
}
}
When I call my route http://shop.test/show_orders I get the following
Exception:"Class soap_client_sales_order does not exist"
Dump:
/home/vagrant/code/shop/vendor/laravel/framework/src/Illuminate/Container/Container.php
}
/**
* Instantiate a concrete instance of the given type.
*
* #param string $concrete
* #return mixed
*
* #throws \Illuminate\Contracts\Container\BindingResolutionException
*/
public function build($concrete)
{
// If the concrete type is actually a Closure, we will just execute it and
// hand back the results of the functions, which allows functions to be
// used as resolvers for more fine-tuned resolution of these objects.
if ($concrete instanceof Closure) {
return $concrete($this, $this->getLastParameterOverride());
}
$reflector = new ReflectionClass($concrete);
// If the type is not instantiable, the developer is attempting to resolve
// an abstract type such as an Interface of Abstract Class and there is
// no binding registered for the abstractions so we need to bail out.
if (! $reflector->isInstantiable()) {
return $this->notInstantiable($concrete);
}
$this->buildStack[] = $concrete;
$constructor = $reflector->getConstructor();
// If there are no constructors, that means there are no dependencies then
// we can just resolve the instances of the objects right away, without
// resolving any other types or dependencies out of these containers.
Hope you can give me a hint.
Thanks,
Manu
Well first of all you never defined $soapClient outside of $app->singleton scope so $soapClient was null and it was a wrong singleton implementation, here is an official guide from laravel docs and the next thing is that you gotta return the singleton inside of singleton scope meaning something like this
/**
* Register the application services.
*
* #return void
*/
public function register()
{
$this->app->singleton('soap_client_sales_order', function () {
...
...
$soapClient->__setLocation(env('BYD_DOMAIN') . $serviceUrl);
return $soapClient;
});
}
Related
I use the ZF3 skeletion application.
I wonder where I am supposed to catch exceptions globally.
Example:
Right now, if I access an invalid route (mysite.com/invalid-route), the application reports an uncaught expection and HTTP response code 200
Fatal error: Uncaught Zend\View\Exception\RuntimeException: No RouteMatch instance provided
I would expect the build-in 404 error page to be triggered.
What am I missing? Can someone point me to the right direction?
The exception is logged properly using the following code:
class Module implements ConfigProviderInterface
{
const VERSION = '3.0.3-dev';
public function onBootstrap()
{
$logger = new Logger();
$writer = new Writer\Stream(__DIR__ . '/../../../data/log/error.log');
$logger->addWriter($writer);
// Log PHP errors
Logger::registerErrorHandler($logger, true);
// Log exceptions
Logger::registerExceptionHandler($logger);
}
This is something you can catch using a Listener, triggered on the early event MvcEvent::EVENT_ROUTE.
I would suggest using a dedicated class & Factory to separate concerns over the usage of the onBootstrap function. You'd do this by registering and "activating" a Listener class, like so:
'listeners' => [
// This makes sure it "gets listened to" from the very start of the application (onBootstrap)
RouteExistsListener::class,
],
'service_manager' => [
'factories' => [
// This is just what you think it is
RouteExistsListener::class => InvokableFactory::class,
],
],
You can use just the InvokableFactory for this Listener, as there are no special requirements.
class RouteExistsListener implements ListenerAggregateInterface
{
/**
* #var array
*/
protected $listeners = [];
/**
* #param EventManagerInterface $events
*/
public function detach(EventManagerInterface $events)
{
foreach ($this->listeners as $index => $listener) {
if ($events->detach($listener)) {
unset($this->listeners[$index]);
}
}
}
/**
* #param EventManagerInterface $events
*/
public function attach(EventManagerInterface $events, $priority = 1)
{
$events->attach(MvcEvent::EVENT_ROUTE, [$this, 'doesRouteExist'], 100);
}
/**
* #param MvcEvent $event
*
* #return void|Response
* #throws Exception
*/
public function doesRouteExist(MvcEvent $event)
{
/** #var TranslatorAwareTreeRouteStack|TreeRouteStack $router */
$router = $event->getRouter();
/** #var Request $request */
$request = $event->getRequest();
/** #var RouteMatch|null $routeExists */
$routeExists = $router->match($request); // Return RouteMatch|null
if ($routeExists instanceof RouteMatch) {
return; // Route exists - nothing to do
}
$url = $router->assemble([], ['name' => 'home']); // Name of your redirect route (ie. not_found/404, or something)
/** #var Response $response */
$response = $event->getResponse();
$response->getHeaders()->addHeaderLine('Location', $url);
$response->setStatusCode(302);
$response->sendHeaders();
$event->getApplication()->getEventManager()->attach(
MvcEvent::EVENT_ROUTE,
function (MvcEvent $event) use ($response) {
$event->stopPropagation();
return $response;
},
-10000
);
return $response;
}
}
NOTE: Used an existing class of my own for the above and modified as I think it should work. However, might contain an error or two ;-) Still, should point you in the right direction I think.
Hello friends i have modified password reset table column name 'created_at' instead of 'created'. if i am changing column name 'created' on migration, But i am getting the error column not found 'created_at'.
\vendor\laravel\framework\src\Illuminate\Auth\Passwords\DatabaseTokenRepository.php
protected function getPayload($email, $token)
{
return ['email' => $email, 'token' => $token, 'created_at' => new Carbon];
}
this is the file coming from column name 'created_at' where i can override this function please suggest me..
I think I've found a way to do this without touching the vendor directory.
For Laravel 5.2
Create a class that extends Illuminate\Auth\Passwords\DatabaseTokenRepository
Overwrite the getPayload() method there
Create a class that extends Illuminate\Auth\Passwords\PasswordBrokerManager
Overwrite the resolve() method to return a new PasswordBroker with your token repository from step 1
Open config/app.php and comment out PasswordResetServiceProvider from the providers array
In your app service provider register an instance of your password broker manager from step 3
$this->app->singleton('auth.password', function ($app) {
return new YourPasswordBrokerManager($app);
});
$this->app->bind('auth.password.broker', function ($app) {
return $app->make('auth.password')->broker();
});
For Lravel 5
Create a class that extends Illuminate\Auth\Passwords\DatabaseTokenRepository
Overwrite the getPayload() method there
Create a class that extends Illuminate\Auth\Passwords\PasswordResetServiceProvider
Overwrite the registerTokenRepository() to return your repository from step 1
Open config/app.php and comment out PasswordResetServiceProvider from the providers array
Add your provider from step 3 to the providers array
Please note that I haven't tested this, but it should work on theory.
Solved: I applied the 5.2 steps in my Laravel 5.4 project:
ForgotPasswordHelperRepository.php
<?PHP
namespace App\Helpers;
use Carbon\Carbon;
class ForgotPasswordHelperRepository extends \Illuminate\Auth\Passwords\DatabaseTokenRepository
{
/**
* Build the record payload for the table.
* I wanted to add an extra column organization_id
* organizationId() is a helper method I created
* #param string $email
* #param string $token
* #return array
*/
protected function getPayload($email, $token)
{
return ['email' => $email, 'token' => $this->hasher->make($token), 'created_at' => new Carbon,'organization_id' => organizationId()];
}
}
PasswordBrokerManagerHeler.php
<?PHP
namespace App\Helpers;
use Closure;
use Illuminate\Auth\Passwords\DatabaseTokenRepository;
use Illuminate\Auth\Passwords\PasswordBroker;
use Illuminate\Support\Str;
class PasswordBrokerManagerHelper extends \Illuminate\Auth\Passwords\PasswordBrokerManager
{
/**
* #inheritDoc
*/
public function sendResetLink(array $credentials)
{
// TODO: Implement sendResetLink() method.
}
/**
* #inheritDoc
*/
public function reset(array $credentials, Closure $callback)
{
// TODO: Implement reset() method.
}
/**
* #inheritDoc
*/
public function validator(Closure $callback)
{
// TODO: Implement validator() method.
}
/**
* #inheritDoc
*/
public function validateNewPassword(array $credentials)
{
// TODO: Implement validateNewPassword() method.
}
/**
* Resolve the given broker.
*
* #param string $name
* #return \Illuminate\Contracts\Auth\PasswordBroker
*
* #throws \InvalidArgumentException
*/
protected function resolve($name)
{
$config = $this->getConfig($name);
if (is_null($config)) {
throw new \InvalidArgumentException("Password resetter [{$name}] is not defined.");
}
// The password broker uses a token repository to validate tokens and send user
// password e-mails, as well as validating that password reset process as an
// aggregate service of sorts providing a convenient interface for resets.
return new PasswordBroker(
$this->createTokenRepository($config),
$this->app['auth']->createUserProvider($config['provider'])
);
}
protected function createTokenRepository(array $config)
{
$key = $this->app['config']['app.key'];
if (Str::startsWith($key, 'base64:')) {
$key = base64_decode(substr($key, 7));
}
$connection = isset($config['connection']) ? $config['connection'] : null;
// return new DatabaseTokenRepository(
return new ForgotPasswordHelperRepository(
$this->app['db']->connection($connection),
$this->app['hash'],
$config['table'],
$key,
$config['expire']
);
}
}
Next, just copy and paste the following in the AppServiceProvider#register method
$this->app->singleton('auth.password', function ($app) { return new PasswordBrokerManagerHelper($app); });
$this->app->bind('auth.password.broker', function ($app) { return $app->make('auth.password')->broker(); });
I have a problem getting a unit test to run for my IndexController class.
The unit test just does the following (inspired from the unit-test tutorial of zf3):
IndexControllerTest.php:
public function testIndexActionCanBeAccessed()
{
$this->dispatch('/', 'GET');
$this->assertResponseStatusCode(200);
$this->assertModuleName('main');
$this->assertControllerName(IndexController::class); // as specified in router's controller name alias
$this->assertControllerClass('IndexController');
$this->assertMatchedRouteName('main');
}
In the Module.php I've some functionality to check if there is a user logged in, else he will be redirected to a login route.
Module.php:
public function onBootstrap(MvcEvent $mvcEvent)
{
/** #var AuthService $authService */
$authService = $mvcEvent->getApplication()->getServiceManager()->get(AuthService::class);
$this->auth = $authService->getAuth(); // returns the Zend AuthenticationService object
// store user and role in global viewmodel
if ($this->auth->hasIdentity()) {
$curUser = $this->auth->getIdentity();
$mvcEvent->getViewModel()->setVariable('curUser', $curUser['system_name']);
$mvcEvent->getViewModel()->setVariable('role', $curUser['role']);
$mvcEvent->getApplication()->getEventManager()->attach(MvcEvent::EVENT_ROUTE, [$this, 'checkPermission']);
} else {
$mvcEvent->getApplication()->getEventManager()->attach(MvcEvent::EVENT_DISPATCH, [$this, 'authRedirect'], 1000);
}
}
The checkPermission method just checks if the user role and the matched route are in the acl storage.
If this fails I will redirect a status code of 404.
Problem: The unit test fails: "Failed asserting response code "200", actual status code is "302"
Therefore the unit test jumps into the else case from my onBootstrap method in the Module.php where the redirect happen.
I did the following setUp in the TestCase but it doesn't work:
public function setUp()
{
// override default configuration values
$configOverrides = [];
$this->setApplicationConfig(ArrayUtils::merge(
include __DIR__ . '/../../../../config/application.config.php',
$configOverrides
));
$user = new Employee();
$user->id = 1;
$user->system_name = 'admin';
$user->role = 'Admin';
$this->authService = $this->prophesize(AuthService::class);
$auth = $this->prophesize(AuthenticationService::class);
$auth->hasIdentity()->willReturn(true);
$auth->getIdentity()->willReturn($user);
$this->authService->getAuth()->willReturn($auth->reveal());
$this->getApplicationServiceLocator()->setAllowOverride(true);
$this->getApplicationServiceLocator()->setService(AuthService::class, $this->authService->reveal());
$this->getApplicationServiceLocator()->setAllowOverride(false);
parent::setUp();
}
Hints are very appreciated
The code might differ a bit from Zend Framework 2 but If you have a simple working example in zf2 maybe I can transform it into zf3 style.
I don't use ZfcUser - just the zend-acl / zend-authentication stuff
After several days of headache I've got a working solution.
First I moved all the code within the onBootstrap to a Listener, because the phpunit mocks are generated after the zf bootstrapping and therefore are non existent in my unit tests.
The key is, that the services are generated in my callable listener method, which is called after zf finished bootstrapping.
Then PHPUnit can override the service with the provided mock.
AuthenticationListener
class AuthenticationListener implements ListenerAggregateInterface
{
use ListenerAggregateTrait;
/**
* #var AuthenticationService
*/
private $auth;
/**
* #var Acl
*/
private $acl;
/**
* Attach one or more listeners
*
* Implementors may add an optional $priority argument; the EventManager
* implementation will pass this to the aggregate.
*
* #param EventManagerInterface $events
* #param int $priority
*
* #return void
*/
public function attach(EventManagerInterface $events, $priority = 1)
{
$this->listeners[] = $events->attach(MvcEvent::EVENT_ROUTE, [$this, 'checkAuthentication']);
}
/**
* #param MvcEvent $event
*/
public function checkAuthentication($event)
{
$this->auth = $event->getApplication()->getServiceManager()->get(AuthenticationService::class);
$aclService = $event->getApplication()->getServiceManager()->get(AclService::class);
$this->acl = $aclService->init();
$event->getViewModel()->setVariable('acl', $this->acl);
if ($this->auth->hasIdentity()) {
$this->checkPermission($event);
} else {
$this->authRedirect($event);
}
}
// checkPermission & authRedirect method
}
Now my onBootstrap got really small, just like ZF wants it. documentation reference
Module.php
public function onBootstrap(MvcEvent $event)
{
$authListener = new AuthenticationListener();
$authListener->attach($event->getApplication()->getEventManager());
}
Finally my mocking in the unit test looks like this:
IndexControllerTest
private function authMock()
{
$mockAuth = $this->getMockBuilder(AuthenticationService::class)->disableOriginalConstructor()->getMock();
$mockAuth->expects($this->any())->method('hasIdentity')->willReturn(true);
$mockAuth->expects($this->any())->method('getIdentity')->willReturn(['id' => 1, 'systemName' => 'admin', 'role' => 'Admin']);
$this->getApplicationServiceLocator()->setAllowOverride(true);
$this->getApplicationServiceLocator()->setService(AuthenticationService::class, $mockAuth);
$this->getApplicationServiceLocator()->setAllowOverride(false);
}
I need to do a custom isGranted method (not using Rbac or acl module from community). So I have a service which provides the functionality. But this code:
if (!$this->userService->isGrantedCustom($this->session->offsetGet('cod_lvl'), 'ZF_INV_HOM')) {
throw new \Exception("you_are_not_allowed", 1);
}
...is duplicated in each controller and each action I have. Parameters are changing of course depends on the permission ('ZF_INV_HOM', 'ZF_TODO_DELETE' ...).
I think it's not a bad idea to do this code before the controller is called, but I can't figure what is the best solution (best architecture), and how to pass those parameters to it (I thought about annotation on controllers but how to handle this ?).
The point is, if I have to modify this code I can't imagine to do that hundreds of times, for each controllers, each action I have I need to have this code in one place.
If you don't want to pollute your Module with all this code you can also make a listener class and attach only the listener in your bootstrap method:
<?php
namespace Application\Listener;
use Application\Service\UserService;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\Mvc\MvcEvent;
use Zend\EventManager\SharedEventManagerInterface;
use Zend\EventManager\SharedListenerAggregateInterface;
use Zend\Authentication\AuthenticationServiceInterface;
class IsAllowedListener implements SharedListenerAggregateInterface
{
/**
* #var AuthenticationServiceInterface
*/
protected $authService;
/**
* #var UserService
*/
protected $userService;
/**
* #var \Zend\Stdlib\CallbackHandler[]
*/
protected $sharedListeners = array();
/**
* #param SharedEventManagerInterface $events
*/
public function attachShared(SharedEventManagerInterface $events)
{
$this->sharedListeners[] = $events->attach(AbstractActionController::class, MvcEvent::EVENT_DISPATCH, array($this, 'isAllowed'), 1000);
}
public function __construct(AuthenticationServiceInterface $authService, UserService $userService ){
$this->authService = $authService;
$this->userService = $userService;
}
/**
* #param MvcEvent $event
*/
protected function isAllowed(MvcEvent $event)
{
$authService = $this->getAuthService();
$identity = $authService->getIdentity();
$userService = $this->getUserService();
if($userService->isGrantedCustom()){
// User is granted we can return
return;
}
// Return not allowed response
}
/**
* #return AuthenticationServiceInterface
*/
public function getAuthService()
{
return $this->authService;
}
/**
* #param AuthenticationServiceInterface $authService
*/
public function setAuthService(AuthenticationServiceInterface $authService)
{
$this->authService = $authService;
}
/**
* #return UserService
*/
public function getUserService()
{
return $this->userService;
}
/**
* #param UserService $userService
*/
public function setUserService(AuthenticationServiceInterface $userService)
{
$this->userService = $userService;
}
}
You need to setup a factory to inject your dependencies:
<?php
namespace Application\Listener;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
/**
* Factory for creating the IsAllowedListener
*/
class IsAllowedListenerFactory implements FactoryInterface
{
/**
* Create the IsAllowedListener
*
* #param ServiceLocatorInterface $serviceLocator
* #return RenderLinksListener
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
$authService = $serviceManager->get('Zend\Authentication\AuthenticationService');
$userService = $serviceLocator->get('Application\Service\UserService');
return new IsAllowedListener($authService, $userService );
}
}
And register all this in config:
'service_manager' => array(
'factories' => array(
'Application\Listener\IsAllowedListener' => 'Application\Listener\IsAllowedListenerFactory'
)
)
And then in bootstrap:
public function onBootstrap(EventInterface $event)
{
$application = $event->getTarget();
$serviceManager = $application->getServiceManager();
$eventManager = $application->getEventManager();
$sharedEventManager = $eventManager->getSharedManager();
$isAllowedListener = $serviceManager->get('Application\Listener\IsAllowedListener')
$sharedEventManager->attachAggregate($isAllowedListener);
}
Instead of using AbstractActionController::class, you could also make a specific class, so you will only listen to instances of that class.
So for example AbstractIsAllowedActionController::class or something like that.
By attaching an event listener to the SharedEventManager you can target all controllers and have the authorization check in just one place.
In this case the target is Zend\Mvc\Controller\AbstractActionController which means any controller extending it will execute the listener. The high priority of this listener will mean that it is executed prior to the target controller action, giving you the chance to handle any requests that have not been authorized.
public function onBootstrap(MvcEvent $event)
{
$application = $event->getApplication();
$eventManager = $application->getEventManager()->getSharedManager();
$eventManager->attach(
\Zend\Mvc\Controller\AbstractActionController::class, // Identity of the target controller
MvcEvent::EVENT_DISPATCH,
[$this, 'isAllowed'],
1000 // high priority
);
}
In each controller there would need to be some way that you can determine which 'resource' is being accessed.
As an example it could implement this interface
interface ResourceInterface
{
// Return a unique key representing the resource
public function getResourceId();
}
The listener could then look like this.
public function isAllowed(MvcEvent $event)
{
$serviceManager = $event->getApplication()->getServiceManager();
// We need the 'current' user identity
$authService = $serviceManager->get('Zend\Authentication\AuthenticationService');
$identity = $authService->getIdentity();
// The service that performs the authorization
$userService = $serviceManager->get('MyModule\Service\UserService');
// The target controller is itself a resource (the thing we want to access)
// in this example it returns an resource id so we know what we want to access
// but you could also get this 'id' from the request or config etc
$controller = $event->getTarget();
if ($controller instanceof ResourceInterface) {
$resourceName = $controller->getResourceId();
// Test the authorization, is UserX allowed resource ID Y
if (empty($resourceName) || $userService->isGrantedCustom($identity, $resourceName)) {
// early exit for success
return;
} else {
// Denied; perhaps trigger a new custom event or return a response
}
}
}
I'm try extends a facede in laravel 4, but I only get a next error on try calling a method.
Non-static method App\Libraries\Theme::setActive() should not be called statically
Edit
After responce of #Antonio, to change the method to static, let the power of using the keyword $ this-> inside the method.
Symfony \ Component \ Debug \ Exception \ FatalErrorException Using
$this when not in object context in $active = $this->ensureRegistered($active);
My code:
<?php namespace App\Libraries;
use Cartalyst\Themes\Facades\Theme as ThemeBag;
class Theme extends ThemeBag {
/**
* Sets the active theme.
*
* #param mixed $active
* #return Cartalyst\Themes\ThemeInterface
*/
public static function setActive($active)
{
$active = $this->ensureRegistered($active);
if ( ! isset($this->themes[$active->getSlug()]))
{
$this->register($active);
}
$this->active = $active;
include $this->getActive()->getPath() . '\\helpers\\composers.php';
}
}
Basically you'll have to extend an existing Facade:
<?php namespace AntonioRibeiro\Libraries;
class MyEventFacade extends Illuminate\Support\Facades\Event {
/**
* Sets the active theme.
*
* #param mixed $active
* #return Cartalyst\Themes\ThemeInterface
*/
public static function setActive($active)
{
/// do what you have to do
}
}
And then replace (or add it as a new one) to your app/config/app.php:
'aliases' => array(
'App' => 'Illuminate\Support\Facades\App',
...
// 'Event' => 'Illuminate\Support\Facades\Event',
'Event' => 'AntonioRibeiro\Libraries\MyEventFacade',
...
'File' => 'Illuminate\Support\Facades\File',
'ActiveSession' => 'AntonioRibeiro\Facades\ActiveSessionFacade',
),
Don't forget do execute 'composer dump-autoload'.
I don't have access to those Cartalyst Themes, but the error you where receiving was related to the method you didn't created as static:
public function setActive($active)
{
}
Shoud be
public static function setActive($active)
{
}
You'll find some good information about it here (make a class extending the Request "Facade"): http://fideloper.com/extend-request-response-laravel