Symfony Components - Routing - php

Question:
Seriously, I'm using it well? It works but I'm not sure about correct way of using Symfony Components. Anybody use these components like me?
I'm not sure about exception handling in dispatch()
I'm not sure about using $_SERVER['REQUEST_URI'] in code below too...
My composer.json
{
"require": {
"symfony/routing": "^3.2"
}
}
My index.php
<?php
include './vendor/autoload.php';
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\HttpFoundation\Request;
class Christopher {
public $context;
public $routes;
public function __construct() {
$context = new RequestContext();
$this->context = $context->fromRequest(Request::createFromGlobals());
$this->routes = new RouteCollection();
}
public function route($methods, $path, $controller) {
$this->routes->add($path, new Route(
$path,
[
'_controller' => $controller
],
[
'id' => '[0-9]'
],
[],
'',
[],
$methods
));
}
public function dispatch() {
$matcher = new UrlMatcher($this->routes, $this->context);
try {
$matcher = $matcher->match($_SERVER['REQUEST_URI']);
call_user_func_array($matcher['_controller'], array_slice($matcher, 1, -1));
} catch (MethodNotAllowedException $e) {
echo 'Route method is not allowed.';
} catch (ResourceNotFoundException $e) {
echo 'Route does not exist.';
}
}
}
$christopher = new Christopher();
$christopher->route('GET', '/posters', function () {
echo '1. Poster';
});
$christopher->route('GET', '/posters/{id}', function ($id) {
echo 'ID - ' . $id;
});
$christopher->dispatch();

Related

How to access PDO instance created with DI in dependencies.php

How do I get the PDO connection in my model class that was created in dependicies.php?
I have Controller and Model classes.
My route:
$app->group('/users', function (Group $group) {
$group->get('', [UsersController::class, 'getAll'], function (Request $request, Response $response) {
return $response;
});
});
My Controller:
namespace App\Application\Controllers;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use App\Application\Models\UsersService;
class UsersController
{
private $_usersSvc;
public function __construct()
{
$this->_usersSvc = new UsersService();
}
public function getAll(Request $request, Response $response)
{
$uri = $request->getUri();
parse_str($uri->getQuery(), $params);
$result = $this->_usersSvc->getAll($params);
$response->getBody()->write(json_encode($result));
return $response;
}
My DI setup in dependencies.php
return function (ContainerBuilder $containerBuilder) {
$containerBuilder->addDefinitions([
LoggerInterface::class => function (ContainerInterface $c) {
$settings = $c->get(SettingsInterface::class);
$loggerSettings = $settings->get('logger');
$logger = new Logger($loggerSettings['name']);
$processor = new UidProcessor();
$logger->pushProcessor($processor);
$handler = new StreamHandler($loggerSettings['path'], $loggerSettings['level']);
$logger->pushHandler($handler);
return $logger;
},
PDO::class => function (ContainerInterface $c) {
$settings = $c->get('settings');
$db = [
'dbname' => $settings['db']['name'],
'user' => $settings['db']['username'],
'pass' => $settings['db']['password'],
'host' => $settings['db']['host']
];
$connection = new PDO("mysql:host=" . $db['host'] . ";port=3306;dbname=" . $db['dbname'], $db['user'], $db['pass']);
$connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$connection->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
return $connection;
},
]);
};
My Model Class
namespace App\Application\Models;
use App\Application\Models\DataObjects;
use Psr\Log\LoggerInterface;
use Psr\Container\ContainerInterface;
use DateTime;
use PDO;
class UsersService extends DataObjects
{
protected $db;
public function __construct()
{
}
public function getAll($params)
{
$orderBy = (isset($params['sortdesc']) && empty($params['sortdesc']) === false) ? $params['sortdesc'] . ' DESC' : null;
if ($orderBy === null) {
$orderBy = (isset($params['sortasc']) && empty($params['sortasc']) === false) ? $params['sortasc'] . ' ASC' : '';
}
return $this->loadAll($orderBy);
}
}
How to access $connection instance from dependencies.php in my model class?

getServiceLocator returning Null under PHPUnittests

I'm trying to test a simple controller that authenticates a user using the LdapAdapter and using the 'ldap' array from the configuration of the Application, but phpunit is returning the following error:
Fatal error: Uncaught Error: Call to a member function get() on null in /var/www/html/app/module/Auth/src/Auth/Controller/AuthController.php:53
Stack trace:
#0 /var/www/html/app/module/Auth/test/AuthTest/Controller/AuthControllerTest.php(37): Auth\Controller\AuthController->authenticate('myuser', 'mypassword')
#1 [internal function]: AuthTest\Controller\AlbumControllerTest->testLoginAction()
#2 /var/www/html/vendor/phpunit/phpunit/src/Framework/TestCase.php(863): ReflectionMethod->invokeArgs(Object(AuthTest\Controller\AlbumControllerTest), Array)
#3 /var/www/html/vendor/phpunit/phpunit/src/Framework/TestCase.php(741): PHPUnit_Framework_TestCase->runTest()
#4 /var/www/html/vendor/phpunit/phpunit/src/Framework/TestResult.php(608): PHPUnit_Framework_TestCase->runBare()
#5 /var/www/html/vendor/phpunit/phpunit/src/Framework/TestCase.php(697): PHPUnit_Framework_TestResult->run(Object(AuthTest\Controller\AlbumControllerTest))
#6 /var/www/html/vendor/phpunit/phpunit/src/Framework/TestSuite.php(733): PHPUnit_Framework_TestCase- in /var/www/html/app/module/Auth/src/Auth/Controller/AuthController.php on line 53
My Controller is the following:
<?php
namespace Auth\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\Authentication\Adapter\Ldap as AuthAdapter;
use Zend\Authentication\AuthenticationService;
use Zend\Authentication\Result;
use Auth\Form\AuthForm;
use Auth\Model\Auth;
class AuthController extends AbstractActionController
{
public function loginAction()
{
$form = new AuthForm();
$request = $this->getRequest();
if ($request->isPost()) {
$auth = new Auth();
$form->setInputFilter($auth->getInputFilter());
$form->setData($request->getPost());
if ($form->isValid()){
$auth->exchangeArray($form->getData());
$values = $form->getData();
$result = $this->authenticate($values['username'], $values['password']);
switch($result->getCode()) {
case Result::SUCCESS:
return $this->redirect()->toRoute('home');
break;
case Result::FAILURE:
break;
}
}
}
return array('form' => $form);
}
public function authenticate($username, $password){
$options = $this->getServiceLocator()->get('Config');
$authAdapter = new AuthAdapter($options['ldap'],
'username',
'password');
$authAdapter
->setIdentity($username)
->setCredential($password);
$auth = new AuthenticationService();
$result = $auth->authenticate($authAdapter);
return $result;
}
private function debug($var){
echo '<pre>';
var_dump($var);
echo '</pre>';
exit();
}
}
The TestCase:
namespace AuthTest\Controller;
use Zend\Test\PHPUnit\Controller\AbstractHttpControllerTestCase;
use Zend\Authentication\AuthenticationService;
use Auth\Controller\AuthController;
class AuthControllerTest extends AbstractHttpControllerTestCase
{
protected $traceError = true;
public function setUp()
{
$this->setApplicationConfig(
include '/var/www/html/app/config/application.config.php'
);
parent::setUp();
}
public function testLoginAction()
{
#Basic Access to the page
$this->dispatch('/login');
$this->assertResponseStatusCode(200);
$data = array(
'identity' => 'myuser',
'credential' => 'mypassword',
);
$auth = new AuthController();
$auth->authenticate($data['identity'], $data['credential']);
$identity = new AuthenticationService();
$this->assertEquals($data['identity'], $identity->getIdentity());
}
}
PHPUnittest's BootStrap:
<?php
namespace AuthTest;
use Zend\Loader\AutoloaderFactory;
use Zend\Mvc\Service\ServiceManagerConfig;
use Zend\ServiceManager\ServiceManager;
use RuntimeException;
error_reporting(E_ALL | E_STRICT);
chdir(__DIR__);
/**
* Test bootstrap, for setting up autoloading
*/
class Bootstrap
{
protected static $serviceManager;
public static function init()
{
$zf2ModulePaths = array(dirname(dirname(__DIR__)));
if (($path = static::findParentPath('vendor'))) {
$zf2ModulePaths[] = $path;
}
if (($path = static::findParentPath('module')) !== $zf2ModulePaths[0]) {
$zf2ModulePaths[] = $path;
}
static::initAutoloader();
// use ModuleManager to load this module and it's dependencies
$config = array(
'module_listener_options' => array(
'module_paths' => $zf2ModulePaths,
),
'modules' => array(
'Auth'
)
);
$serviceManager = new ServiceManager(new ServiceManagerConfig());
$serviceManager->setService('ApplicationConfig', $config);
$serviceManager->get('ModuleManager')->loadModules();
static::$serviceManager = $serviceManager;
}
public static function chroot()
{
$rootPath = dirname(static::findParentPath('module'));
chdir($rootPath);
}
public static function getServiceManager()
{
return static::$serviceManager;
}
protected static function initAutoloader()
{
$vendorPath = static::findParentPath('vendor');
if (file_exists($vendorPath.'/autoload.php')) {
include $vendorPath.'/autoload.php';
}
if (! class_exists('Zend\Loader\AutoloaderFactory')) {
throw new RuntimeException(
'Unable to load ZF2. Run `php composer.phar install`'
);
}
AutoloaderFactory::factory(array(
'Zend\Loader\StandardAutoloader' => array(
'autoregister_zf' => true,
'namespaces' => array(
__NAMESPACE__ => __DIR__ . '/' . __NAMESPACE__,
),
),
));
}
protected static function findParentPath($path)
{
$dir = __DIR__;
$previousDir = '.';
while (!is_dir($dir . '/' . $path)) {
$dir = dirname($dir);
if ($previousDir === $dir) {
return false;
}
$previousDir = $dir;
}
return $dir . '/' . $path;
}
}
Bootstrap::init();
Bootstrap::chroot();
In the functional tests it works as expected, but on php unittests the error occurs in the line 53 '$options = $this->getServiceLocator()->get('Config');'.
So, how can use or set ServiceLocator to work with phpunittests?
After so much struggling on this doubt and other tests which a controller that uses an ServiceLocator, I figure that the correct way to test a controllers Action is to Mock the object or/and his methods.
I don't feel totally comfortable with this solution, but for now, this is what it is.
An other feel is that mocking an behaviour of the code is something like that breaks the DRY principle: instead of use the real code I just create a mock that half behaves like it :S
Anyway, the following code does the job for me:
$authControllerMock = $this->getMockBuilder('Auth\Controller\AuthController')
->disableOriginalConstructor()
->getMock();
$authControllerMock->expects($this->any())
->method('authenticate')
->will($this->returnValue(true);
$serviceManager = $this->getApplicationServiceLocator();
$serviceManager->setAllowOverride(true);
$serviceManager->setService('Auth\Controller\Auth', $authControllerMock);
$this->dispatch('/usermgr');
self::assertMatchedRouteName('usermgr');
self::assertControllerClass('AuthController');
self::assertControllerName('auth\controller\auth');
self::assertResponseStatusCode('200');

Twig: Pull Object variable from page into custom tag

I have making a custom twig tag called "story_get_adjacent" to get the next/prev articles based on the input id. But for the life of me I cant get the actual data from the object pulled into the tag for look up. it always gives me back the name not the data. I know this can be done because i tested it with the set tag and it returns the data not the name. Thoughts????
Object on page
Object >>
Title = "This is a test story"
StoryID = 1254
Content ....
tag usage Example:
{% story_get_adjacent Object.StoryID as adjacent %}
Twig Extension:
class Story_Get_Adjacent_TokenParser extends Twig_TokenParser
{
public function parse(Twig_Token $token)
{
$parser = $this->parser; //story_get_adjacent
$stream = $parser->getStream(); // space
$value = $parser->getExpressionParser()->parseExpression(); //story id
$names = array();
try {
$as = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); //as
$ObjName = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); //object name
array_push($names, $ObjName);
} catch (Exception $e) {
throw new Exception( 'error: ' . $e);
}
$stream->expect(Twig_Token::BLOCK_END_TYPE);
return new Story_Get_Adjacent_Node($names, $value, $token->getLine(), $this->getTag());
}
public function getTag()
{
return 'story_get_adjacent';
}
}
Twig Extension:
class Story_Get_Adjacent_Node extends Twig_Node
{
public function __construct($name, Twig_Node_Expression $value, $line, $tag = null)
{
parent::__construct(array('value' => $value), array('name' => $name), $line, $tag);
}
public function compile (Twig_Compiler $compiler)
{
$Name = $this->getAttribute('name')[0];
$StoryAutoID = $this->getNode('value')->getAttribute('value');
$compiler->addDebugInfo($this);
$compiler->write('$context[\''. $Name .'\'] = NewsController::TwigStoryGetAdjacent("'.$StoryAutoID.'");')->raw("\n");
}
}
Alain Tiemblo solves your problem. However, why do you make your life so hard? Why don't you simply use a twig function?
{% set adjacent = story_get_adjacent(Object.StoryID) %}
class StoryExtension extends \Twig_Extension
{
public function getFunctions()
{
return array(
\Twig_SimpleFunction('story_get_adjacent', array($this, 'getAdjacent'), array('is_safe' => array('html'))),
);
}
public function getAdjacent($storyId)
{
return NewsController::TwigStoryGetAdjacent($storyId);
}
public function getName()
{
return 'story';
}
}
(the is_safe option tells twig it's safe HTML, so you don't have to disable escaping when using {{ story_get_adjacent(Object.StoryID) }}).
The problem is located in the compiler, when you try to access the value attribute of your expression.
The expression parser need to be subcompiled into an expression in PHP.
As the compiled expression is a non-evaluated expression, you shouldn't put quotes ( ' ) when you call TwigStoryGetAdjacent.
Try with the following class:
<?php
class Story_Get_Adjacent_Node extends Twig_Node
{
public function __construct($name, Twig_Node_Expression $value, $line, $tag = null)
{
parent::__construct(array ('value' => $value), array ('name' => $name), $line, $tag);
}
public function compile(Twig_Compiler $compiler)
{
$Name = reset($this->getAttribute('name'));
$compiler->addDebugInfo($this);
$compiler
->write("\$context['$Name'] = NewsController::TwigStoryGetAdjacent(")
->subcompile($this->getNode('value'))
->write(");")
->raw("\n")
;
}
}
Working demo
test.php
<?php
require(__DIR__ . '/vendor/autoload.php');
require("TestExtension.php");
require("TestTokenParser.php");
require("TestNode.php");
class NewsController
{
static public function TwigStoryGetAdjacent($id)
{
return "I return the story ID = {$id}.";
}
}
$loader = new Twig_Loader_Filesystem('./');
$twig = new Twig_Environment($loader, array (
'cache' => __DIR__ . '/gen'
));
$object = new \stdClass;
$object->StoryID = 42;
$twig->addExtension(new Test_Extension());
echo $twig->render("test.twig", array ('Object' => $object));
test.twig
{% story_get_adjacent Object.StoryID as adjacent %}
{{ adjacent }}
TestExtension.php
<?php
class Test_Extension extends \Twig_Extension
{
public function getTokenParsers()
{
return array (
new Test_TokenParser(),
);
}
public function getName()
{
return 'test';
}
}
TestTokenParser.php
<?php
class Test_TokenParser extends Twig_TokenParser
{
public function parse(Twig_Token $token)
{
$parser = $this->parser; //story_get_adjacent
$stream = $parser->getStream(); // space
$value = $parser->getExpressionParser()->parseExpression(); //story id
$names = array();
try {
$as = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); //as
$ObjName = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); //object name
array_push($names, $ObjName);
} catch (Exception $e) {
throw new Exception( 'error: ' . $e);
}
$stream->expect(Twig_Token::BLOCK_END_TYPE);
return new Test_Node($names, $value, $token->getLine(), $this->getTag());
}
public function getTag()
{
return 'story_get_adjacent';
}
}
TestNode.php
<?php
class Test_Node extends Twig_Node
{
public function __construct($name, Twig_Node_Expression $value, $line, $tag = null)
{
parent::__construct(array ('value' => $value), array ('name' => $name), $line, $tag);
}
public function compile(Twig_Compiler $compiler)
{
$Name = reset($this->getAttribute('name'));
$compiler->addDebugInfo($this);
$compiler
->write("\$context['$Name'] = NewsController::TwigStoryGetAdjacent(")
->subcompile($this->getNode('value'))
->write(");")
->raw("\n")
;
}
}
Run
$ composer require "twig/twig" "~1.0"
$ php test.php
Result
I return the story ID = 42.
Bonus The compiled doDisplay method corresponding to the template
protected function doDisplay(array $context, array $blocks = array())
{
// line 1
$context['adjacent'] = NewsController::TwigStoryGetAdjacent($this->getAttribute((isset($context["Object"]) ? $context["Object"] : null), "StoryID", array()) );
// line 2
echo twig_escape_filter($this->env, (isset($context["adjacent"]) ? $context["adjacent"] : null), "html", null, true);
}

How to set-up a generic leveraged logging using Monolog?

I am writting a console application with Symfony2 components, and I want to add distinct logging channels for my services, my commands and so on. The problem: to create a new channel requires to create a new instance of Monolog, and I don't really know how to handle this in a generic way, and without needing to pass the stream handler, a channel and the proper code to bind the one and the other inside all services.
I did the trick using debug_backtrace():
public function log($level, $message, array $context = array ())
{
$trace = array_slice(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3), 1);
$caller = $trace[0]['class'] !== __CLASS__ ? $trace[0]['class'] : $trace[1]['class'];
if (!array_key_exists($caller, $this->loggers))
{
$monolog = new Monolog($caller);
$monolog->pushHandler($this->stream);
$this->loggers[$caller] = $monolog;
}
$this->loggers[$caller]->log($level, $message, $context);
}
Whatever from where I call my logger, it creates a channel for each class that called it. Looks cool, but as soon as a logger is called tons of time, this is performance-killing.
So here is my question:
Do you know a better generic way to create one distinct monolog channel per class that have a logger property?
The above code packaged for testing:
composer.json
{
"require" : {
"monolog/monolog": "~1.11.0"
}
}
test.php
<?php
require('vendor/autoload.php');
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
class Test
{
public function __construct($logger)
{
$logger->info("test!");
}
}
class Hello
{
public function __construct($logger)
{
$logger->log(Monolog\Logger::ALERT, "hello!");
}
}
class LeveragedLogger implements \Psr\Log\LoggerInterface
{
protected $loggers;
protected $stream;
public function __construct($file, $logLevel)
{
$this->loggers = array ();
$this->stream = new StreamHandler($file, $logLevel);
}
public function alert($message, array $context = array ())
{
$this->log(Logger::ALERT, $message, $context);
}
public function critical($message, array $context = array ())
{
$this->log(Logger::CRITICAL, $message, $context);
}
public function debug($message, array $context = array ())
{
$this->log(Logger::DEBUG, $message, $context);
}
public function emergency($message, array $context = array ())
{
$this->log(Logger::EMERGENCY, $message, $context);
}
public function error($message, array $context = array ())
{
$this->log(Logger::ERROR, $message, $context);
}
public function info($message, array $context = array ())
{
$this->log(Logger::INFO, $message, $context);
}
public function log($level, $message, array $context = array ())
{
$trace = array_slice(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3), 1);
$caller = $trace[0]['class'] !== __CLASS__ ? $trace[0]['class'] : $trace[1]['class'];
if (!array_key_exists($caller, $this->loggers))
{
$monolog = new Logger($caller);
$monolog->pushHandler($this->stream);
$this->loggers[$caller] = $monolog;
}
$this->loggers[$caller]->log($level, $message, $context);
}
public function notice($message, array $context = array ())
{
$this->log(Logger::NOTICE, $message, $context);
}
public function warning($message, array $context = array ())
{
$this->log(Logger::WARNING, $message, $context);
}
}
$logger = new LeveragedLogger('php://stdout', Logger::DEBUG);
new Test($logger);
new Hello($logger);
Usage
ninsuo:test3 alain$ php test.php
[2014-10-21 08:59:04] Test.INFO: test! [] []
[2014-10-21 08:59:04] Hello.ALERT: hello! [] []
What would you think about making the decision which logger should be used right before the consumers are created? This could be easily accomplished with some kind of DIC or maybe a factory.
<?php
require('vendor/autoload.php');
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Psr\Log\LoggerInterface;
use Monolog\Handler\HandlerInterface;
class Test
{
public function __construct(LoggerInterface $logger)
{
$logger->info("test!");
}
}
class Hello
{
public function __construct(LoggerInterface $logger)
{
$logger->log(Monolog\Logger::ALERT, "hello!");
}
}
class LeveragedLoggerFactory
{
protected $loggers;
protected $stream;
public function __construct(HandlerInterface $streamHandler)
{
$this->loggers = array();
$this->stream = $streamHandler;
}
public function factory($caller)
{
if (!array_key_exists($caller, $this->loggers)) {
$logger = new Logger($caller);
$logger->pushHandler($this->stream);
$this->loggers[$caller] = $logger;
}
return $this->loggers[$caller];
}
}
$loggerFactory = new LeveragedLoggerFactory(new StreamHandler('php://stdout', Logger::DEBUG));
new Test($loggerFactory->factory(Test::class));
new Hello($loggerFactory->factory(Hello::class));
I finally created a MonologContainer class that extends the standard Symfony2 container, and injects a Logger to LoggerAware services. Overloading the get() method of the service container, I can get the service's ID, and use it as a channel for the logger.
<?php
namespace Fuz\Framework\Core;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Monolog\Handler\HandlerInterface;
use Monolog\Logger;
use Psr\Log\LoggerAwareInterface;
class MonologContainer extends ContainerBuilder
{
protected $loggers = array ();
protected $handlers = array ();
protected $processors = array ();
public function __construct(ParameterBagInterface $parameterBag = null)
{
parent::__construct($parameterBag);
}
public function pushHandler(HandlerInterface $handler)
{
foreach (array_keys($this->loggers) as $key)
{
$this->loggers[$key]->pushHandler($handler);
}
array_unshift($this->handlers, $handler);
return $this;
}
public function popHandler()
{
if (count($this->handlers) > 0)
{
foreach (array_keys($this->loggers) as $key)
{
$this->loggers[$key]->popHandler();
}
array_shift($this->handlers);
}
return $this;
}
public function pushProcessor($callback)
{
foreach (array_keys($this->loggers) as $key)
{
$this->loggers[$key]->pushProcessor($callback);
}
array_unshift($this->processors, $callback);
return $this;
}
public function popProcessor()
{
if (count($this->processors) > 0)
{
foreach (array_keys($this->loggers) as $key)
{
$this->loggers[$key]->popProcessor();
}
array_shift($this->processors);
}
return $this;
}
public function getHandlers()
{
return $this->handlers;
}
public function get($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE)
{
$service = parent::get($id, $invalidBehavior);
return $this->setLogger($id, $service);
}
public function setLogger($id, $service)
{
if ($service instanceof LoggerAwareInterface)
{
if (!array_key_exists($id, $this->loggers))
{
$this->loggers[$id] = new Logger($id, $this->handlers, $this->processors);
}
$service->setLogger($this->loggers[$id]);
}
return $service;
}
}
Usage example:
test.php
#!/usr/bin/env php
<?php
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Fuz\Framework\Core\MonologContainer;
if (!include __DIR__ . '/vendor/autoload.php')
{
die('You must set up the project dependencies.');
}
$container = new MonologContainer();
$loader = new YamlFileLoader($container, new FileLocator(__DIR__));
$loader->load('services.yml');
$handler = new StreamHandler(__DIR__ ."/test.log", Logger::WARNING);
$container->pushHandler($handler);
$container->get('my.service')->hello();
services.yml
parameters:
my.service.class: Fuz\Runner\MyService
services:
my.service:
class: %my.service.class%
MyService.php
<?php
namespace Fuz\Runner;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
class MyService implements LoggerAwareInterface
{
protected $logger;
public function setLogger(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function hello()
{
$this->logger->alert("Hello, world!");
}
}
Demo
ninsuo:runner alain$ php test.php
ninsuo:runner alain$ cat test.log
[2014-11-06 08:18:55] my.service.ALERT: Hello, world! [] []
You can try this
<?php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\FirePHPHandler;
class Loggr{
private static $_logger;
public $_instance;
public $_channel;
private function __construct(){
if(!isset(self::$_logger))
self::$_logger = new Logger('Application Log');
}
// Create the logger
public function logError($error){
self::$_logger->pushHandler(new StreamHandler(LOG_PATH . 'application.'. $this->_channel . '.log', Logger::ERROR));
self::$_logger->addError($error);
}
public function logInfo($info){
self::$_logger->pushHandler(new StreamHandler(LOG_PATH . 'application.'. $this->_channel . '.log', Logger::INFO));
self::$_logger->addInfo($info);
}
public static function getInstance($channel) {
$_instance = new Loggr();
$_instance->_channel = strtolower($channel);
return $_instance;
}
}
and can be consumed as
class LeadReport extends Controller{
public function __construct(){
$this->logger = Loggr::getInstance('cron');
$this->logger->logError('Error generating leads');
}
}

symfony2 login using 3 parameters (username, password and company)

I have been giving myself a headache over this all day,
I've been learning Symfony over the past couple days and my problem is simple, I need to be able to log in with 3 parameters (username, password and company)
The same question has been asked here, however the asker never provided a solution:
Custom Login using a Third Parameter
I have tried to start by cloning Symfony's inbuilt login by implementing a Token, Listener, Provider and Factory but whenever I insert my key into security.yml and try to log in it takes me to /login (No route found for "GET /login")
Anyone care to make my day?
EDIT:
OK, I Just tried again and now I have this error:
Catchable Fatal Error: "Argument 1 passed to Test\SampleBundle\Security\Authentication\Provider\MyProvider::__construct() must be an instance of Symfony\Component\Security\Core\User\UserCheckerInterface, instance of Symfony\Bridge\Doctrine\Security\User\EntityUserProvider given"
Again all I'm trying to do is duplicate Symfonys current user/password login functionality
MyToken.php:
namespace Test\SampleBundle\Security\Authentication\Token;
use Symfony\Component\Security\Core\Authentication\Token\AbstractToken;
class MyToken extends AbstractToken
{
private $credentials;
private $providerKey;
public function __construct($user, $credentials, $providerKey, array $roles = array())
{
parent::__construct($roles);
if (empty($providerKey)) {
throw new \InvalidArgumentException('$providerKey must not be empty.');
}
$this->setUser($user);
$this->credentials = $credentials;
$this->providerKey = $providerKey;
parent::setAuthenticated(count($roles) > 0);
}
public function setAuthenticated($isAuthenticated)
{
if ($isAuthenticated) {
throw new \LogicException('Cannot set this token to trusted after instantiation.');
}
parent::setAuthenticated(false);
}
public function getCredentials()
{
return $this->credentials;
}
public function getProviderKey()
{
return $this->providerKey;
}
public function eraseCredentials()
{
parent::eraseCredentials();
$this->credentials = null;
}
public function serialize()
{
return serialize(array($this->credentials, $this->providerKey, parent::serialize()));
}
public function unserialize($serialized)
{
list($this->credentials, $this->providerKey, $parentStr) = unserialize($serialized);
parent::unserialize($parentStr);
}
}
MyListener.php:
<?php
namespace Test\SampleBundle\Security\Firewall;
use Symfony\Component\Security\Http\Firewall\AbstractAuthenticationListener;
use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
use Symfony\Component\HttpFoundation\Request;
use Psr\Log\LoggerInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface;
use Symfony\Component\Security\Http\HttpUtils;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Test\SampleBundle\Security\Authentication\Token\MyToken;
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class MyListener extends AbstractAuthenticationListener
{
private $csrfProvider;
public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager, SessionAuthenticationStrategyInterface $sessionStrategy, HttpUtils $httpUtils, $providerKey, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options = array(), LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, CsrfProviderInterface $csrfProvider = null)
{
parent::__construct($securityContext, $authenticationManager, $sessionStrategy, $httpUtils, $providerKey, $successHandler, $failureHandler, array_merge(array(
'username_parameter' => '_username' . 'b',
'password_parameter' => '_password',
'csrf_parameter' => '_csrf_token',
'intention' => 'authenticate',
'post_only' => true,
), $options), $logger, $dispatcher);
$this->csrfProvider = $csrfProvider;
}
protected function requiresAuthentication(Request $request)
{
if ($this->options['post_only'] && !$request->isMethod('POST')) {
return false;
}
return parent::requiresAuthentication($request);
}
protected function attemptAuthentication(Request $request)
{
if (null !== $this->csrfProvider) {
$csrfToken = $request->get($this->options['csrf_parameter'], null, true);
if (false === $this->csrfProvider->isCsrfTokenValid($this->options['intention'], $csrfToken)) {
throw new InvalidCsrfTokenException('Invalid CSRF token.');
}
}
if ($this->options['post_only']) {
$username = trim($request->request->get($this->options['username_parameter'], null, true));
$password = $request->request->get($this->options['password_parameter'], null, true);
} else {
$username = trim($request->get($this->options['username_parameter'], null, true));
$password = $request->get($this->options['password_parameter'], null, true);
}
$request->getSession()->set(SecurityContextInterface::LAST_USERNAME, $username);
return $this->authenticationManager->authenticate(new MyToken($username, $password, $this->providerKey));
}
}
MyProivder.php:
namespace Test\SampleBundle\Security\Authentication\Provider;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserCheckerInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\Exception\AuthenticationServiceException;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Test\SampleBundle\Security\Authentication\Token\MyToken;
use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
class MyProvider implements AuthenticationProviderInterface
{
private $hideUserNotFoundExceptions;
private $userChecker;
private $providerKey;
public function __construct(UserCheckerInterface $userChecker, $providerKey, $hideUserNotFoundExceptions = true)
{
if (empty($providerKey)) {
throw new \InvalidArgumentException('$providerKey must not be empty.');
}
$this->userChecker = $userChecker;
$this->providerKey = $providerKey;
$this->hideUserNotFoundExceptions = $hideUserNotFoundExceptions;
}
public function authenticate(TokenInterface $token)
{
if (!$this->supports($token)) {
return null;
}
$username = $token->getUsername();
if (empty($username)) {
$username = 'NONE_PROVIDED';
}
try {
$user = $this->retrieveUser($username, $token);
} catch (UsernameNotFoundException $notFound) {
if ($this->hideUserNotFoundExceptions) {
throw new BadCredentialsException('Bad credentials', 0, $notFound);
}
$notFound->setUsername($username);
throw $notFound;
}
if (!$user instanceof UserInterface) {
throw new AuthenticationServiceException('retrieveUser() must return a UserInterface.');
}
try {
$this->userChecker->checkPreAuth($user);
$this->checkAuthentication($user, $token);
$this->userChecker->checkPostAuth($user);
} catch (BadCredentialsException $e) {
if ($this->hideUserNotFoundExceptions) {
throw new BadCredentialsException('Bad credentials', 0, $e);
}
throw $e;
}
$authenticatedToken = new MyToken($user, $token->getCredentials(), $this->providerKey, $user->getRoles());
$authenticatedToken->setAttributes($token->getAttributes());
return $authenticatedToken;
}
public function supports(TokenInterface $token)
{
return $token instanceof MyToken && $this->providerKey === $token->getProviderKey();
}
/**
* {#inheritdoc}
*/
protected function checkAuthentication(UserInterface $user, MyToken $token)
{
$currentUser = $token->getUser();
if ($currentUser instanceof UserInterface) {
if ($currentUser->getPassword() !== $user->getPassword()) {
throw new BadCredentialsException('The credentials were changed from another session.');
}
} else {
if ("" === ($presentedPassword = $token->getCredentials())) {
throw new BadCredentialsException('The presented password cannot be empty.');
}
if (!$this->encoderFactory->getEncoder($user)->isPasswordValid($user->getPassword(), $presentedPassword, $user->getSalt())) {
throw new BadCredentialsException('The presented password is invalid.');
}
}
}
/**
* {#inheritdoc}
*/
protected function retrieveUser($username, MyToken $token)
{
$user = $token->getUser();
if ($user instanceof UserInterface) {
return $user;
}
try {
$user = $this->userProvider->loadUserByUsername($username);
if (!$user instanceof UserInterface) {
throw new AuthenticationServiceException('The user provider must return a UserInterface object.');
}
return $user;
} catch (UsernameNotFoundException $notFound) {
$notFound->setUsername($username);
throw $notFound;
} catch (\Exception $repositoryProblem) {
$ex = new AuthenticationServiceException($repositoryProblem->getMessage(), 0, $repositoryProblem);
$ex->setToken($token);
throw $ex;
}
}
}
MyFactory.php
<?php
namespace Test\SampleBundle\DependencyInjection\Security\Factory;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;
class MyFactory implements SecurityFactoryInterface
{
public function __construct()
{
$this->addOption('username_parameter', '_username');
$this->addOption('password_parameter', '_password');
$this->addOption('csrf_parameter', '_csrf_token');
$this->addOption('intention', 'authenticate');
$this->addOption('post_only', true);
}
public function getPosition()
{
return 'pre_auth';
}
public function getKey()
{
return 'mylogin';
}
public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint)
{
$providerId = 'security.authentication.provider.mylogin.'.$id;
$container
->setDefinition($providerId, new DefinitionDecorator('mylogin.security.authentication.provider'))
->replaceArgument(0, new Reference($userProvider))
;
$listenerId = 'security.authentication.listener.mylogin.'.$id;
$listener = $container->setDefinition($listenerId, new DefinitionDecorator('mylogin.security.authentication.listener'));
return array($providerId, $listenerId, $defaultEntryPoint);
}
public function addConfiguration(NodeDefinition $node)
{
}
protected function createEntryPoint($container, $id, $config, $defaultEntryPoint)
{
$entryPointId = 'security.authentication.form_entry_point.'.$id;
$container
->setDefinition($entryPointId, new DefinitionDecorator('security.authentication.form_entry_point'))
->addArgument(new Reference('security.http_utils'))
->addArgument($config['login_path'])
->addArgument($config['use_forward'])
;
return $entryPointId;
}
final public function addOption($name, $default = null)
{
$this->options[$name] = $default;
}
}
Services.yml:
services:
mylogin.security.authentication.provider:
class: Test\SampleBundle\Security\Authentication\Provider\MyProvider
arguments: ['', %kernel.cache_dir%/security/nonces]
mylogin.security.authentication.listener:
class: Test\SampleBundle\Security\Firewall\MyListener
arguments: [#security.context, #security.authentication.manager]
And finally injector:
namespace Test\SampleBundle;
use Test\SampleBundle\DependencyInjection\Security\Factory\MyFactory;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class TestSampleBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$extension = $container->getExtension('security');
$extension->addSecurityListenerFactory(new MyFactory());
}
}
There may be a few errors in the above code as I have been playing around with stuff to get it working

Categories