How to inject ServiceManager into a user defined class - zend-framework2

In the doc it's said:"By default, the Zend Framework MVC registers an initializer that will inject the ServiceManager instance, which is an implementation of Zend\ServiceManager\ServiceLocatorInterface, into any class implementing Zend\ServiceManager\ServiceLocatorAwareInterface."
so I tried this:
interface ModelResourceInterface extends ServiceLocatorAwareInterface
{
}
interface ServiceModelResourceInterface extends ModelResourceInterface
{
public function fetch($uri, $method, $parameters, $options, $encodeType);
}
namespace Ssports\Model\Resource\Service\Http;
use Ssports\Model\Resource\Service\ServiceModelResourceInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\Http\Client;
use Zend\Http\Request;
use Ssports\Model\Resource\Service\ConnectionException;
abstract class AbstractHttpServiceModelResource implements ServiceModelResourceInterface
{
/**
*
* #var Zend\ServiceManager\ServiceLocatorInterface;
*/
protected $serviceLocator;
/**
* Constructor
*/
function __construct()
{
$this->init();
}
/**
* Extend Constructor
*/
public function init()
{}
/**
* (non-PHPdoc)
*
* #see \Zend\ServiceManager\ServiceLocatorAwareInterface::setServiceLocator()
*
*/
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
}
/**
* (non-PHPdoc)
*
* #see \Ssports\Model\Resource\Service\ServiceModelResourceInterface::fetch()
*
*/
public function fetch($uri, $method, $parameters = null, $options = null, $encodeType = null)
{
try {
//something raise \RuntimeException
} catch (\RuntimeException $e) {
$this->getServiceLocator()->get('Log\Web');
throw new ConnectionException();
}
}
/**
* (non-PHPdoc)
*
* #see \Zend\ServiceManager\ServiceLocatorAwareInterface::getServiceLocator()
*
*/
public function getServiceLocator()
{
return $this->serviceLocator;
}
}
I extend this abstract class with some model resource class, and run it, and an exception throw to say that I'm calling get on a non-object.
Seem that the service manager is not being injected to my abstract class, and the return of getServiceLocator is NULL.
Any thing I missed to make it right?

Have you tried to use the service locator trait?
It can be found in \Zend\ServiceManager\ServiceLocatorAwareTrait
However this requires PHP 5.4 to work...
To use a trait do the following
class Class1
{
use Zend\ServiceManager\ServiceLocatorAwareTrait;
}
You can access the service locator then, or at least that is how i had to do it when i needed to load the service locator.

Related

Symfony 5.4 with OAuth Keycloak connect Error must implement AuthenticatorInterface

I'm trying to implement an OAuth connection with Keycloak on Symfony 5.4, when I display a page of my application, all works fine, I have the keycloak login page, but after validate, I have this error :
Argument 1 passed to Symfony\Component\Security\Http\Authenticator\Debug\TraceableAuthenticator::__construct() must implement interface Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface, instance of App\Security\KeycloakAuthenticator given, called in /var/www/oauth-symfony/vendor/symfony/security-http/Authenticator/Debug/TraceableAuthenticatorManagerListener.php on line 60
Obviously I tried to add implements AuthenticatorInterface I hadd to add authenticate() and createToken() methods, but even with that the implementations still not works.
KeycloakAuthenticator.php
<?php
namespace App\Security;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use KnpU\OAuth2ClientBundle\Security\Authenticator\SocialAuthenticator;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
/**
* Class KeycloakAuthenticator
*/
class KeycloakAuthenticator extends SocialAuthenticator
{
/**
* ClientRegistry: the OAuth client manager
* EntityManagerInterface: to read/write in database
* RouterInterface: read a route/URL
*/
private $clientRegistry;
private $em;
private $router;
public function __construct(
ClientRegistry $clientRegistry,
EntityManagerInterface $em,
RouterInterface $router
)
{
$this->clientRegistry = $clientRegistry;
$this->em = $em;
$this->router = $router;
}
public function start(Request $request, \Symfony\Component\Security\Core\Exception\AuthenticationException $authenticationException = null): RedirectResponse
{
return new RedirectResponse(
'/oauth/login', // might be the site, where users choose their oauth provider
Response::HTTP_TEMPORARY_REDIRECT
);
}
public function supports(Request $request): ?bool
{
return $request->attributes->get('_route') === 'oauth_check';
}
public function getCredentials(Request $request)
{
return $this->fetchAccessToken($this->getKeycloakClient());
}
public function getUser($credentials, \Symfony\Component\Security\Core\User\UserProviderInterface $userProvider)
{
$keycloakUser = $this->getKeycloakClient()->fetchUserFromToken($credentials);
//existing user ?
$existingUser = $this
->em
->getRepository(User::class)
->findOneBy(['keycloakId' => $keycloakUser->getId()]);
if ($existingUser) {
return $existingUser;
}
// if user exist but never connected with keycloak
$email = $keycloakUser->getEmail();
/** #var User $userInDatabase */
$userInDatabase = $this->em->getRepository(User::class)
->findOneBy(['email' => $email]);
if($userInDatabase) {
$userInDatabase->setKeycloakId($keycloakUser->getId());
$this->em->persist($userInDatabase);
$this->em->flush();
return $userInDatabase;
}
//user not exist in database
$user = new User();
$user->setKeycloakId($keycloakUser->getId());
$user->setEmail($keycloakUser->getEmail());
$user->setRoles(['ROLE_USER']);
$this->em->persist($user);
$this->em->flush();
return $user;
}
public function onAuthenticationFailure(Request $request, \Symfony\Component\Security\Core\Exception\AuthenticationException $exception)
{
$message = strtr($exception->getMessageKey(), $exception->getMessageData());
return new Response($message, Response::HTTP_FORBIDDEN);
}
public function onAuthenticationSuccess(Request $request, \Symfony\Component\Security\Core\Authentication\Token\TokenInterface $token, string $providerKey)
{
// change "app_homepage" to some route in your app
$targetUrl = $this->router->generate('dashboard');
return new RedirectResponse($targetUrl);
}
/**
* #return \KnpU\OAuth2ClientBundle\Client\Provider\KeycloakClient
*/
private function getKeycloakClient()
{
return $this->clientRegistry->getClient('keycloak');
}
}
security.yml
security:
enable_authenticator_manager: true
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
providers:
app_user_provider:
entity:
class: App\Entity\User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
provider: app_user_provider
entry_point: form_login
form_login:
login_path: oauth_login
custom_authenticator: App\Security\KeycloakAuthenticator
access_control:
# - { path: ^/admin, roles: ROLE_ADMIN }
# - { path: ^/profile, roles: ROLE_USER }
- { path: ^/dashboard, roles: ROLE_USER }
OAuthController.php
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Annotation\Route;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use KnpU\OAuth2ClientBundle\Client\Provider\KeycloakClient;
class OAuthController extends AbstractController
{
/**
* #Route("/oauth/login", name="oauth_login")
*/
public function index(ClientRegistry $registry): RedirectResponse
{
/**#var KeycloakClient $client */
$client = $registry->getClient('keycloak');
return $client->redirect();
}
/**
* #Route("/oauth/callback", name="oauth_check")
*/
public function check(){}
}
You need replace the class to extend by OAuth2Authenticator 1.
Add the typing ": ?Response" on onAuthenticationSuccess and onAuthenticationFailure
And create the function "authenticate" to return PassportInterface

How To Use toRoute in ZF2 View Helper

I want to use toRoute or redirect controller plugin in my view helper. I know View helpers extend functionality on the view layer and are for reusability throughout our application. Controller plugins extend functionality on the controller layer. But I want any other solution to do that.
Here is my view helper:
<?php
namespace Application\View\Helper;
use Zend\View\Helper\AbstractHelper;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class GetSiteSettings extends AbstractHelper implements ServiceLocatorAwareInterface {
protected $serviceLocator;
/**
* Set the service locator.
*
* #param ServiceLocatorInterface $serviceLocator
* #return CustomHelper
*/
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
return $this;
}
/**
* Get the service locator.
*
* #return \Zend\ServiceManager\ServiceLocatorInterface
*/
public function getServiceLocator()
{
return $this->serviceLocator;
}
public function __invoke()
{
$redirect = $this->redirect()->toRoute('my_account');
/*$sm = $this->getServiceLocator()->getServiceLocator();
$config = $sm->get('Config');
return $config['site_settings'];*/
}
}
?>
In the above code, the line:
$redirect = $this->redirect()->toRoute('my_account');
is really not working and I also tried several things to achieve it, but nothing helped.
I've got it at my own. We can get the controller plugin manager service and then use any plugin.
$sm = $this->getServiceLocator()->getServiceLocator();
$redirect = $sm->get('ControllerPluginManager')->get('redirect');
$redirect->toRoute('my_account')

catching the login event of ZfcUser with Zend framework 2

i am using zfcUser with Zend Framework 2.
i want to capture the login event so that i can do something with it and also redirect the user to a different page.
i know how to catch the register event. ie i place a onBootstrap in my module.php file and then do the following
$em->attach('ZfcUser\Form\RegisterFilter','init',function($e)
the entire function is like this;
public function onBootstrap(MVCEvent $e)
{
$eventManager = $e->getApplication()->getEventManager();
$em = $eventManager->getSharedManager();
$em->attach(
'ZfcUser\Form\RegisterFilter',
'init',
function($e)
{ }
}
However, when i tried to catch the login event i did not have any results. i.e
$zfcServiceEvents->attach('login.post', function($e) {
echo "loggedn"; die();
});
would really appreciate some help.
thank you.
edit:
this is how i called the registered event ( i know how to called the event for a user who has just registered. but i want to call the event when a user has just succesfully logged in
public function onBootstrap(MVCEvent $e)
{
$zfcServiceEvents = $e->getApplication()->getServiceManager()->get('zfcuser_user_service')->getEventManager();
$zfcServiceEvents->attach('register', function($e) {
$form = $e->getParam('form');
$user = $e->getParam('user');
RESPONSE TO cptnk CODE
in response to the answer given by cptnk i tried the following code but it did not work;
public function onBootstrap(MVCEvent $e)
{
$sharedManager = $e->getApplication()->getEventManager()->getSharedManager();
$serviceManager = $e->getApplication()->getServiceManager();
$loginFunction = function ($e) use ($serviceManager) {
echo "eventCaptured" ; die();
};
$sharedManager->attach('ZfcUser\Service\User', 'login.post', $loginFunction);
}
i am still not able to capture the login event.Any ideas?
cptnik had a good suggestion; he asked whether i had overridden the ZFcUser-service. in responce i clarfiy that i had overridden the zfcuser. below is the code i used;
'zfcuser' => array(
// telling ZfcUser to use our own class
'user_entity_class' => 'BaseModel\Entity\User',
// telling ZfcUserDoctrineORM to skip the entities it defines
'enable_default_entities' => false,
),
/**
* Listen to the bootstrap event
*
* #return array
*/
public function onBootstrap(MvcEvent $e)
{
$serviceManager = $e->getApplication()->getServiceManager()
$loginFunction = function ($e) use ($serviceManager) {
// do something
};
$sharedManager->attach('ZfcUser\Service\User', 'login.post', $loginFunction);
}
Your code does not show what $zfcServiceEvents is or where it came from I assume it is a event manager referencing the zfcuser events. My approach is a little different since I attach a event to the zf2 shared manager. Maybe you have problems regarding that piece of code?
You can grab the sharedEventManager like so:
$sharedManager = $e->getApplication()->getEventManager()->getSharedManager();
EDIT: I was a little unclear on how to get the $serviceManager and the application within the Module.php onBootstrap function.
Try this
$events = $e->getApplication()->getEventManager()->getSharedManager();
// Handle login
$events->attach('ZfcUser\Authentication\Adapter\AdapterChain', 'authenticate.success', function($e) {
$userId = $e->getIdentity();
// do some stuff
});
In your Module.php add this:
public function onBootstrap(MvcEvent $e) {
$em = $e->getApplication()->getEventManager();
$UserListener = $e->getApplication()->getServiceManager()->get('UserListener');
$em->attachAggregate($UserListener);
}
and create src/User/Listener/UserListener.php with below code:
namespace LcUser\Listener;
use Zend\EventManager\AbstractListenerAggregate;
use Zend\EventManager\EventManagerInterface;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class LcUserListener extends AbstractListenerAggregate implements ServiceLocatorAwareInterface {
/**
* #var ServiceLocatorInterface
*/
protected $serviceManager;
/**
*
* #param ServiceLocatorInterface $serviceLocator
*/
public function setServiceLocator(ServiceLocatorInterface $serviceLocator) {
$this->serviceManager = $serviceLocator;
}
/**
*
* #return \Zend\ServiceManager\ServiceLocatorInterface
*/
public function getServiceLocator() {
return $this->serviceManager;
}
public function attach(EventManagerInterface $events) {
$sharedManager = $events->getSharedManager();
$this->listeners[] = $sharedManager->attach('ZfcUser\Authentication\Adapter\AdapterChain', 'authenticate.success', array($this, 'userLog'));
}
public function userLog(Event $e) {
$em = $this->getServiceLocator()->get('doctrine.entitymanager.orm_default');
$userlog = new UserLogin(); // User Login is my User Log table to record login details as below
$user = $em->getRepository('MyUser\Entity\User')
->findOneBy(array('id' => $e->getIdentity()));
$userlog->setGroupId($user->getId());
$userlog->setUserId($e->getIdentity());
$userlog->setEmail($_POST['identity']);
$userlog->setIpAddresses($_SERVER['SERVER_ADDR']);
$em->persist($userlog);
$em->flush();
}
}

How to create a service layer in zend framwork two?

I need to create a service layer for Zend framework two controller functions in order to decouple the services from controllers.
You're going to need to use the ServiceManager (SM) in order to make this work properly.
This is just an example of how I have done it:
In your ModuleName/src/ModuleName/ create a folder named Service and create your ExampleService.php, Example:
namespace ModuleName\Service;
class ExampleService
{
public function SomeFunctionNameHere()
{
echo 'Hello World';
}
}
Now edit your Module.php and add the Service Layer to your invokables, IE:
public function getServiceConfig()
{
return array(
'invokables' => array(
'ModuleName\Service\ExampleService' => 'ModuleName\Service\ExampleService',
),
);
}
Now edit your ModuleNameController.php
protected $service_example;
public function indexAction()
{
$service = $this->getServiceExample()->SomeFunctionNameHere();
}
private function getServiceExample()
{
if (!$this->service_example) {
$this->service_example = $this->getServiceLocator()->get('ModuleName\Service\ExampleService');
}
return $this->service_example;
}
This should get you started.
Depending on the functionality you are looking for from your service, you might be able to create a custom Controller Plugin. For example, here's a custom controller plugin I wrote to get a user's access level.
Application/Controller/Plugin/GetAccessLevel.php
namespace Application\Controller\Plugin;
use Zend\Mvc\Controller\Plugin\AbstractPlugin;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
class GetAccessLevel extends AbstractPlugin implements ServiceLocatorAwareInterface
{
/**
* Set the service locator.
*
* #param ServiceLocatorInterface $serviceLocator
* #return GetAccessLevel
*/
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
return $this;
}
/**
* Get the service locator.
*
* #return \Zend\ServiceManager\ServiceLocatorInterface
*/
public function getServiceLocator()
{
return $this->serviceLocator;
}
/**
* Takes an array of role objects and returns access level
*
* #param array of MyModule\Entity\Role objects
* #return int Access Level
*/
public function __invoke(array $roles)
{
// Default access level
$accesslevel = 0;
// Get Service Locator for view helpers
$controllerPluginManager = $this->getServiceLocator();
// Get application service manager
$serviceManager = $controllerPluginManager->getServiceLocator();
// Get application config
$config = $serviceManager->get('Config');
// Get the role associated with full access from config
$fullAccessRole = $config['appSettings']['full_access_role'];
// Does user have the role for full access?
foreach ($roles as $roleObject) {
if($roleObject->getName() == $fullAccessRole) {
$accesslevel = 1;
break;
}
}
// Return access level
return $accesslevel;
}
}
Then add the plugin to the configuration.
./module/Application/config/module.config.php
'controller_plugins' => array(
'invokables' => array(
'getAccessLevel' => 'Application\Controller\Plugin\GetAccessLevel'
)
),
Now every controller will have access to this plugin.
Some Controller
public function someAction() {
$accessLevel = $this->getAccesslevel(array('User Role Entities Go Here'));
}

How to handle File Uploads with Doctrine in Symfony2

I am trying to upload image files with Doctrine in Symfony2 but I am getting the following error.: SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'name' cannot be null.
Here is my Entity class
<?php
// src/Acme/DemoBundle/Entity/Document.php
namespace Acme\DemoBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
* #ORM\HasLifecycleCallbacks
* #ORM\Table(name="document")
*/
class Document
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
public $id;
/**
* #ORM\Column(type="string", length=255)
* #Assert\NotBlank
*/
public $name;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
public $path;
/**
* #Assert\File(maxSize="6000000")
*/
public $file;
public function getUploadRootDir()
{
return '/uploads';
}
/**
* Get id
*
* #return integer $id
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*/
public function setName($name = 'akshaya')
{
$this->name = $name;
}
/**
* Get name
*
* #return string $name
*/
public function getName()
{
return $this->name;
}
/**
* Set path
*
* #param string $path
*/
public function setPath($path)
{
$this->path = $path;
}
/**
* Get path
*
* #return string $path
*/
public function getPath()
{
return $this->path;
}
/**
* #ORM\PrePersist()
*/
public function preUpload()
{
if ($this->file) {
$this->setPath($this->file->guessExtension());
}
}
/**
* #ORM\PostPersist()
*/
public function upload()
{
if (!$this->file) {
return;
}
try{
$this->file->move($this->getUploadRootDir(), $this->id.'.'.$this->file->guessExtension());
}
catch(FilePermissionException $e)
{
return false;
}
catch(\Exception $e)
{
throw new \Exception($e->getMessage());
}
unset($this->file);
}
/**
* #ORM\PreRemove()
*/
public function removeUpload()
{
if ($file = $this->getFullPath()) {
unlink($file);
}
}
public function getFullPath()
{
return null === $this->path ? null : $this->getUploadRootDir().'/'.$this->id.'.'.$this->path;
}
}
Here is the related Controller class
<?php
namespace Acme\DemoBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Acme\DemoBundle\Form\ContactForm;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Imagine\Gd\Imagine;
use Imagine\Image\Box;
use Acme\DemoBundle\Entity\Document;
class FileUploadController extends Controller
{
protected $file;
protected $document;
public function indexAction()
{
$this->document = new Document();
$form = $this->get('form.factory')
->createBuilder('form')
->add('name','text')
->add('file','file')
->getForm();
$request = $this->get('request');
if ($request->getMethod() === 'POST') {
$form->bindRequest($request);
if ($form->isValid()) {
$em = $this->get('doctrine.orm.entity_manager');
$this->document->upload();
$em->persist($this->document);
$em->flush();
$this->get('session')->setFlash('notice', 'The file is uploaded!');
}
}
return $this->render('AcmeDemoBundle:FileUpload:index.html.twig',
array("form"=>$form->createView()));
}
}
This error is not related to the upload,
The upload seems to work, but it's the insert in the database who has a problem.
The value you have provided for name is null or empty. Your database schema don't allow null value for name column.
I see 3 possible solutions:
add this annotation to the $name property: #ORM\Column(type="string", length=255, nullable="true")
provide a value for the name field, enter a value when you submit your form.
set a default name when constructing your object:
public function __construct() {
$this->name = 'foo';
}

Resources