Maybe someone can help me
I am not familiar with symfony.
There is running Symfony 3.3.9 with Smarty 3.1.27
I want to inject something to the Session Handling, so each time session is started with
new Session() or
$session = $this->container->get('session');
different session values are given
for example
<?php
namespace AppBundle\Components;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface;
use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface;
use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface;
class MDSession extends Session {
private $domain = null;
private $mandant = null;
const DEFAULT_THEME = '_default';
public function __construct(SessionStorageInterface $storage = null,AttributeBagInterface $attributes = null,FlashBagInterface $flashes = null)
{
parent::__construct($storage, $attributes, $flashes);
$this->getDomain();
/**
* check if session is set and the same requested domain given
*/
if(!$this->_get('domain') || $this->_get('domain') != $this->domain)
{
$this->mandant = $this->getMandant();
/**
* set session here
*/
$this->_set('domain', $this->domain);
$this->_set('mandant', $this->mandant['id']);
$this->_set('theme', $this->mandant['theme']);
}
}
public function _set($name=null,$value=null)
{
parent::set($name,$value);
}
public function _get($name)
{
parent::get($name);
}
/**
* HostnameLookups must be set to On in Apache
*/
private function getDomain()
{
$this->domain = strtolower($_SERVER["HTTP_HOST"]);
}
private function getMandant()
{
/**
* do something here
*/
}
}
How to set config.yml or services.yml to get it working ?
at the moment I do it with an EventListener like this.
I hope this is the right way
namespace AppBundle\EventListener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
class SessionHandler
{
const DEFAULT_THEME = '_default';
const DEFAULT_MANDANT = '1';
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
$session = $request->getSession();
$host = $request->getHost();
if(!empty($session->get('mandant')) OR $session->get('host') != $host)
{
//check DB for mandant
//.....
//setting session
$session->set('host', $host);
$session->set('mandant', self::DEFAULT_MANDANT);
$session->set('theme', self::DEFAULT_THEME);
}
if (!$event->isMasterRequest()) {
// don't do anything if it's not the master request
return;
}
// ...
}
}
Related
I'm on symfony 5.4
I didn't understand what symfony really need in order to correct this deprecation:
Since symfony/security-csrf 5.3: Using the "Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage" without a session has no effect and is deprecated. It will throw a "Symfony\Component\HttpFoundation\Exception\SessionNotFoundException" in Symfony 6.0
1x in MeansCablesControllerTest::TestDatagridAdd from App\Tests\Controller
My function in tests/Controller/MeansBenchesControllerTest.php (WebTestCase) :
function datagridAddUpdate($controllerName, $dataArray)
{
$client = static::createClient();
$usersRepository = static::getContainer()->get(UsersRepository::class);
$testUserAdmin = $usersRepository->find(1);
$client->loginUser($testUserAdmin);
$csrfToken = $client->getContainer()->get('security.csrf.token_manager')->getToken($controllerName.'Token_item');
$dataArray['_token'] = $csrfToken;
$crawler = $client->request('POST', '/datagridAddUpdate/'.$controllerName,$dataArray, [], ['HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest']);
$this->assertResponseIsSuccessful('Status code 2xx pour datagridAdd : '.$controllerName);
}
Running this before building the form will make sure there is a session available for it:
$request = new Request();
$request->setSession(new Session(new MockArraySessionStorage()));
self::getContainer()->get(RequestStack::class)->push($request);
Unfortunately, #bart's answer didn't work for me. It effectively suppressed the deprecation warning, but it creates a separate session from the loginUser() session. My goal was to log in, and then be able to set session values on the common logged in session.
I figured out a workaround for my use case, documented here: https://github.com/symfony/symfony/discussions/46961
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\BrowserKit\Cookie;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\User\UserInterface;
abstract class AbstractWebTestCase extends WebTestCase
{
private KernelBrowser $client;
private SessionInterface $session;
public function setUp(): void
{
parent::setUp();
$this->client = static::createClient();
...
}
...
/**
* This replicates static::createClient()->loginUser()
* Inspect that method, as there are additional checks there that may be necessary for your use case.
* The magic here is tracking an internal $session object that can be updated as needed.
*/
protected function loginUser(UserInterface $user): void
{
$token = new TestBrowserToken($user->getRoles(), $user, $firewallContext);
$container = static::getContainer();
$container->get('security.untracked_token_storage')->setToken($token);
$this->session = $container->get('session.factory')->createSession();
$this->setLoginSessionValue('_security_'.$firewallContext, serialize($token));
$domains = array_unique(array_map(function (Cookie $cookie) {
return $cookie->getName() === $this->session->getName() ? $cookie->getDomain() : '';
}, $this->client->getCookieJar()->all())) ?: [''];
foreach ($domains as $domain) {
$cookie = new Cookie($this->session->getName(), $this->session->getId(), null, null, $domain);
$this->client->getCookieJar()->set($cookie);
}
return $this;
}
/** #param mixed $value */
protected function setLoginSessionValue(string $name, $value): self
{
if (isset($this->session)) {
$this->session->set($name, $value);
$this->session->save();
return $this;
}
throw new \LogicException("loginUser() must be called to initialize session");
}
...
}
And now we can update the session:
use Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage;
class MyWebTest extends AbstractWebTestCase
{
public function testSomething(): void
{
$user = ...;
$this->loginUser($user);
// Technically, you don't need to generate a real token here, and instead could use any test string
$tokenId = ...;
$csrfToken = static::getContainer()->get('security.csrf.token_generator')->generateToken();
$this->setLoginSessionValue(SessionTokenStorage::SESSION_NAMESPACE . "/$tokenId", $csrfToken);
// Now you can make raw POST requests without crawling to the form page first!
}
}
I'm developing an application with symfony3, in which I'm tracking routes and saving them into my db with a service. This service is saving also a null route when there is an exception ( 500 , 404 ... ) so I want to add something to avoid saving this.
This is my service:
<?php
namespace Alpha\VisitorTrackingBundle\EventListener;
use Alpha\VisitorTrackingBundle\Entity\Lifetime;
use Alpha\VisitorTrackingBundle\Entity\PageView;
use Alpha\VisitorTrackingBundle\Entity\Session;
use Doctrine\ORM\EntityManager;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Class VisitorTrackingSubscriber
* #package Alpha\VisitorTrackingBundle\EventListener
*
* Tracks the source of a session and each pageview in that session. Primary use of this is to be able to see the path through our site each
*/
class VisitorTrackingSubscriber implements EventSubscriberInterface
{
/**
* #var \Doctrine\ORM\EntityManager
*/
protected $em;
/**
* #var Session
*/
protected $session;
/**
* #var Lifetime
*/
protected $lifetime;
protected $routesExclues;
protected $utmCodes = [
"utm_source",
"utm_medium",
"utm_campaign",
"utm_term",
"utm_content"
];
const COOKIE_LIFETIME = "lifetime";
const COOKIE_SESSION = "session";
public function __construct(EntityManager $em, $routesExclues)
{
$this->em = $em;
$this->routesExclues = $routesExclues;
}
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
if (substr($request->get("_route"), 0, 1) == "_" ) {
//these are requests for assets/symfony toolbar etc. Not relevant for our tracking
return;
}
if ($request->cookies->has(self::COOKIE_SESSION) and !$this->requestHasUTMParameters($request)) {
$this->session = $this->em->getRepository("AlphaVisitorTrackingBundle:Session")->find($request->cookies->get(self::COOKIE_SESSION));
if ($this->session instanceof Session) {
$this->lifetime = $this->session->getLifetime();
} else {
$this->generateSessionAndLifetime($request);
}
} else {
$this->generateSessionAndLifetime($request);
}
}
public function onKernelResponse(FilterResponseEvent $event)
{
if (false === $this->session instanceof Session) {
return;
}
$request = $event->getRequest();
$response = $event->getResponse();
$exception = $event->getKernel()->handle($request)->getStatusCode();
if (substr($request->get("_route"), 0, 1) == "_" or ($response instanceof RedirectResponse) ) {
//these are requests for assets/symfony toolbar etc. Not relevant for our tracking
return;
}
$pageView = new PageView();
$route = $request->get('_route');
$pageView->setUrl($route);
if (strpos($request->getRequestUri(), $this->routesExclues) === false // && $response->getStatusCode() === '200'
)
$this->session->addPageView($pageView);
$this->em->flush($this->session);
if ($this->requestHasUTMParameters($request)) {
$this->setUTMSessionCookies($request, $response);
}
if (!$request->cookies->has(self::COOKIE_LIFETIME)) {
$response->headers->setCookie(new Cookie(self::COOKIE_LIFETIME, $this->lifetime->getId(), 0, "/", null, false, false));
}
//no session cookie set OR session cookie value != current session ID
if (!$request->cookies->has(self::COOKIE_SESSION) or ($request->cookies->get(self::COOKIE_SESSION) != $this->session->getId())) {
$response->headers->setCookie(new Cookie(self::COOKIE_SESSION, $this->session->getId(), 0, "/", null, false, false));
}
}
protected function requestHasUTMParameters(Request $request)
{
foreach ($this->utmCodes as $code) {
if ($request->query->has($code)) {
return true;
}
}
return false;
}
protected function setUTMSessionCookies(Request $request, Response $response)
{
foreach ($this->utmCodes as $code) {
$response->headers->clearCookie($code);
if ($request->query->has($code)) {
$response->headers->setCookie(new Cookie($code, $request->query->get($code), 0, "/", null, false, false));
}
}
}
/**
* #return Session
*/
public function getSession()
{
return $this->session;
}
/**
* #return Lifetime
*/
public function getLifetime()
{
return $this->lifetime;
}
public static function getSubscribedEvents()
{
return array(
KernelEvents::RESPONSE => ['onKernelResponse', 1024],
KernelEvents::REQUEST => ["onKernelRequest", 16]
);
}
private function generateSessionAndLifetime(Request $request)
{
if (strpos($request->headers->get("User-Agent"), "bot") === false && $request->getClientIp() !== "::1"
) {
$lifetime = false;
if ($request->cookies->has(self::COOKIE_LIFETIME)) {
$lifetime = $this->em->getRepository("AlphaVisitorTrackingBundle:Lifetime")->find($request->cookies->get(self::COOKIE_LIFETIME));
}
if (!$lifetime) {
$lifetime = new Lifetime();
$this->em->persist($lifetime);
}
$session = new Session();
$session->setIp($request->getClientIp() ?: "");
$session->setReferrer($request->headers->get("Referer") ?: "");
$session->setUserAgent($request->headers->get("User-Agent") ?: "");
$session->setQueryString($request->getQueryString() ?: "");
$session->setLoanTerm($request->query->get("y") ?: "");
$session->setRepApr($request->query->has("r") ? hexdec($request->query->get("r")) / 100 : "");
$details = json_decode(file_get_contents("http://ipinfo.io/{$request->getClientIp()}/json"));
$names = json_decode(file_get_contents("http://country.io/names.json"), true);
$session->setPays($names[$details->country]);
foreach ($this->utmCodes as $code) {
$method = "set" . \Doctrine\Common\Inflector\Inflector::classify($code);
$session->$method($request->query->get($code) ?: "");
}
$lifetime->addSession($session);
$this->em->flush();
$this->session = $session;
$this->lifetime = $lifetime;
}
}
}
I have added something to get statusCode from response but it always
returns 200. ( if statusCode !== '200' it must not save route).
Anyone to help me to get an idea or a solution to my problem? Thanks
I am using symfony 3 and trying to get access to the class I declared in
src/AppBundle/Service/ApiEngine.php
namespace AppBundle\Service;
use DateTime;
class ApiEngine {
private $api_handler;
public function __construct($username, bool $repos) {
$client = new \GuzzleHttp\Client();
$request = 'https://api.github.com/users/' . $username;
$request .= ($repos) ? '/repos' : "";
$res = $client->request('GET', $request);
$this->api_handler = json_decode($res->getBody()->getContents(), true);
}
public function getProfileData() {
return [ /*some data*/ ];
}
}
I declared this file in
config/service.yml
service:
*
*
*
api:
class: AppBundle\Service\ApiEngine
arguments: ["#username", "#repos"]
In controller I am trying to use some of the ApiEngine methods like this:
src/AppBundle/Controller/GitApiController.php
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class GitApiController extends Controller {
/**
* #Route("/{username}", name ="gitapi", defaults={"username": "symfony"})
*/
public function gitApiAction($username) {
$api = $this->get('api')->__construct($username, false)->getProfileData();
return $this->render('gitapi/index.html.twig', $api);
} }
But it gives me this error:
(1/1) ServiceNotFoundException The service "api" has a dependency on a
non-existent service "username".
I advise you to change your class into this for example:
private function __construct($username, bool $repos) {
$client = new \GuzzleHttp\Client();
$request = 'https://api.github.com/users/' . $username;
$request .= ($repos) ? '/repos' : "";
$res = $client->request('GET', $request);
$this->api_handler = json_decode($res->getBody()->getContents(), true);
}
public static function createApiEngine($username, bool $repos)
{
return new self($username, $bool);
}
After inside your controller you can do this:
$api = ApiEngine::createApiEngine($username, false);
$api->getProfileData();
Into your controller you need to insert a use for ApiEngine, in this use case you don't need dependency injection so inside your services.yml remove arguments please
I'm trying to wrap my head around how to inject Laravel's model observers with repositories.
Currently, I have this setup.
UserPetServiceProvider.php
<?php namespace Bunny\Providers;
use Illuminate\Support\ServiceProvider;
use Bunny\Observers\Pet\UserPetObserver;
class UserPetServiceProvider extends ServiceProvider {
public function register()
{
// User pets
$this->app->bind('Bunny\Repos\Pet\IUserPetRepo', 'Bunny\Repos\Pet\UserPetRepo');
// User pet layers
$this->app->bind('Bunny\Repos\Pet\IUserPetLayerRepo', 'Bunny\Repos\Pet\UserPetLayerRepo');
// User pet markings
$this->app->bind('Bunny\Repos\Pet\IUserPetMarkingRepo', 'Bunny\Repos\Pet\UserPetMarkingRepo');
$this->app->events->subscribe(new UserPetObserver());
}
}
It binds all the interfaces and repositories fine and would with the observer, but I need repository injection which I do in the constructor. Nothing is being passed in the constructor so it would explain why it fails.
UserPetRepo.php
<?php namespace Bunny\Repos\Pet;
use Bunny\Repos\BaseRepo;
use Bunny\Models\Pet\UserPet;
use Bunny\Repos\User\IUserRepo;
use Bunny\Repos\Breed\IStageRepo;
use Bunny\Repos\Breed\IBreedLayerRepo;
use Illuminate\Config\Repository as Config;
use Illuminate\Support\Str as String;
use Illuminate\Session\SessionManager as Session;
use Illuminate\Events\Dispatcher;
class UserPetRepo extends BaseRepo implements IUserPetRepo {
public function __construct(UserPet $pet, IUserRepo $user, IStageRepo $stage, IBreedLayerRepo $layer, Config $config, String $string, Session $session, Dispatcher $events)
{
$this->model = $pet;
$this->user = $user;
$this->stage = $stage;
$this->layer = $layer;
$this->config = $config;
$this->string = $string;
$this->session = $session;
$this->events = $events;
$this->directory = $this->config->get('pathurl.userpets');
$this->url = $this->config->get('pathurl.userpets_url');
}
/**
* Create new user pet
* #param attributes Attributes
*/
public function createWithImage( array $attributes, array $colors, $domMarkings = null, $domMarkingColors = null, $recMarkings = null, $recMarkingColors = null )
{
$this->model->name = $attributes['name'];
$this->model->breed_id = $attributes['breed_id'];
$this->model->user_id = $this->user->getId();
$this->model->stage_id = $this->stage->getBaby()->id;
// Get image
$image = $this->layer->compile(
$attributes['breed_id'],
'baby',
$colors,
$domMarkings,
$domMarkingColors
);
// Write image and set
$file = $this->writeImage( $image );
if( ! $file )
{
return false;
}
$this->model->base_image = $file;
$this->model->image = $file;
if( ! $this->model->save() )
{
return false;
}
$this->events->fire('userpet.create', array($this->model));
return $this->model;
}
/**
* Write image
* #param image Imagick Object
*/
protected function writeImage( $image )
{
$fileName = $this->string->random(50) . '.png';
if( $image->writeImage( $this->directory . $fileName ) )
{
return $fileName;
}
$this->model->errors()->add('writeImage', 'There was an error writing your image. Please contact an administrator');
return false;
}
}
UserPetObserver.php
use Bunny\Repos\Pet\IUserPetLayerRepo;
use Bunny\Repos\Pet\IUserPetMarkingRepo;
use Bunny\Observers\BaseObserver;
class UserPetObserver extends BaseObserver {
public function __construct(IUserPetLayerRepo $layers, IUserPetMarkingRepo $markings)
{
$this->layers = $layers;
$this->markings = $markings;
}
/**
* After create
*/
public function onCreate( $model )
{
$this->layers->user_pet_id = $model->id;
dd(Input::all());
$this->layers->breed_layer_id = $model->id;
}
public function subscribe( $event )
{
$event->listen('userpet.create', 'UserPetObserver#onCreate');
}
}
It throws this as the error:
Argument 1 passed to
Bunny\Observers\Pet\UserPetObserver::__construct() must be an instance
of Bunny\Repos\Pet\IUserPetLayerRepo, none given, called in H:\WD
SmartWare.swstor\HALEY-HP\Source2\bunny-meadows\app\Bunny\Providers\UserPetServiceProvider.php
on line 22 and defined
Which makes sense since I'm not passing anything in the constructor. So I try to pass my repository manually.
$this->app->events->subscribe(new UserPetObserver(new UserPetLayerRepo, new UserPetMarkingRepo));
But then it throws errors of UserPetLayerRepo needing injections... and it just chains on and on. Is there anyway of doing this that I'm just overthinking?
Thanks.
EDIT:::
This is the only thing I could think of doing. This seems like a really ugly/bad way of doing it though:
$this->app->events->subscribe(new UserPetObserver($this->app->make('Bunny\Repos\Pet\UserPetLayerRepo'), $this->app->make('Bunny\Repos\Pet\UserPetMarkingRepo')));
Any other ideas?
Try just:
$this->app->events->subscribe($this->app->make('UserPetObserver'));
When Laravel makes the UserPetObserver object, it will read the type-hinted dependencies in the constructor and automatically make them, as well.
I'm developing a WebApp which (as usual) must support customer specific functionalities.
To achieve it I plan to set the customer name in the local app configuration (config/autoload/local.php )
configuration file so that I can use it to call the specialized code later on.
The module folder structure is this:
/module/Application
/module/Application/config
/module/Application/src
/module/Application/src/Application
/module/Application/src/Application/Controller
/module/Application/src/Application/Controller/[customer_instance_name]
/module/Application/src/Application/Model
/module/Application/src/Application/Model/[customer_instance_name]
/module/Application/view
/module/Application/view/Application
/module/Application/view/Application/[action]
/module/Application/view/Application/[action]/[customer_instance_name]
Using a custom ViewModel I inject the specific customer name to the template path:
namespace Application\Model;
use Zend\View\Model\ViewModel;
use Zend\View\Resolver\TemplatePathStack;
use Zend\Mvc\Service\ViewTemplatePathStackFactory;
class MyViewModel extends ViewModel
{
private $customInstanceName;
private $pathStack;
/**
* Constructor
*
* #param null|array|Traversable $variables
* #param array|Traversable $options
*/
public function __construct($variables = null, $options = null)
{
parent::__construct ( $variables, $options );
$serviceLocator = MySingleton::instance()->serviceLocator;
$factory = new ViewTemplatePathStackFactory();
$this->pathStack = $factory->createService($serviceLocator);
$config = $serviceLocator->get('config');
if (isset($config['custom_instance_name']) AND ($config['custom_instance_name']!='')) {
$this->customInstanceName = $config['custom_instance_name'];
} else {
$this->customInstanceName = false;
}
}
/**
* Set the template to be used by this model
*
* #param string $template
* #return ViewModel
*/
public function setTemplate($template)
{
$this->template = (string) $template;
if ( $this->customInstanceName === false) {
return $this;
}
$pathComponents = explode('/', (string) $template);
$last = array_pop($pathComponents);
array_push($pathComponents, $this->customInstanceName);
array_push($pathComponents, $last);
$customTemplate = implode('/', $pathComponents);
if ($this->pathStack->resolve($customTemplate) !== false) {
$this->template = $customTemplate;
}
return $this;
}
}
Using the "Decorator Pattern" I can achieve the same customization level on my Models.
I'm having problem to handle specific behavior. In this case I plan to create custom Controllers extending
my base controller class, but I'unable to call those controllers since the routing is defined on the module
config (and I was unable to change it in runtime).
My questions are:
1) Is this approach correct, or there is a better way to do it?
2) If the approach is correct, how can I define a custom router to be used when the ServiceManager reads my routing config?
Just found a solution. Will register it here hoping someone will benefit from it.
All I had to do was to create a specific router class with a match method which returns the correct routing target for each customer controller, and add it to my module.config.php as the type for each action.
namespace TARGETNAMESPACE;
use Traversable;
use Zend\Mvc\Router\Exception;
use Zend\Mvc\Router\Http\RouteInterface;
use Zend\Mvc\Router\Http\RouteMatch;
use Zend\Mvc\Router\Http\Literal;
use Zend\Stdlib\ArrayUtils;
use Zend\Stdlib\RequestInterface as Request;
class MyRouterLiteral extends Literal {
public function match(Request $request, $pathOffset = null) {
if (! method_exists($request, 'getUri')) {
return null;
}
$uri = $request->getUri();
$path = $uri->getPath();
if ($pathOffset !== null) {
if ($pathOffset >= 0 && strlen($path) >= $pathOffset && ! empty($this->route)) {
if (strpos($path, $this->route, $pathOffset) === $pathOffset) {
return new RouteMatch($this->getDefaults(), strlen($this->route));
}
}
return null;
}
if ($path === $this->route) {
return new RouteMatch($this->getDefaults(), strlen($this->route));
}
return null;
}
private function getDefaults() {
$aux = explode('\\', $this->defaults['controller']);
$last = array_pop($aux);
array_push($aux, '[CUSTOM_INSTANCE_NAME]');
array_push($aux, '[CUSTOM_INSTANCE_NAME]'.$last);
$result = $this->defaults;
$result['controller'] = implode('\\', $aux);
return $result;
}
}
To address all cases I had to create a second custom router (for segment routes) which follows the same rules and can be easily derived from the above code.