I'm trying to set up a Contact Form and all is going well. Set up my Controller with ->send(), all works fine (takes a bit of time). When I set it up to work with ->queue(), seems to work fine (no delay), job is set up, mail is sent when I dispatch. But this time my mail template does not include the data sent to the Mailer.
My Controller:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Mail\Contact;
use Illuminate\Support\Facades\Mail;
class PagesController extends Controller
{
public function sendContact(Request $request)
{
Mail::to('webform#email.com')
->queue(new Contact($request));
return redirect('/contact')->with('status', 'Message sent. Thanks!');
}
}
My Mailer (App\Mail\Contact):
class Contact extends Mailable
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct()
{
}
/**
* Build the message.
*
* #return $this
*/
public function build(Request $request)
{
$subject = 'Web Message from: ' . $request->name;
return $this->from('myemail#email.com')
->subject($subject)
->view('emails.contact-template')
->with([
'name' =>$request->name,
'email' => $request->email,
'message' => $request->message,
'date' => $request->date,
]);
}
}
The problem was that I needed to declare the variables as public. Below is the solution that eventually worked:
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Http\Request;
use Illuminate\Contracts\Queue\ShouldQueue;
class Contact extends Mailable
{
use Queueable, SerializesModels;
public $request;
public $name;
public $from;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct(Request $request)
{
$this->request = $request->all();
$this->name = $request->name;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
$subject = 'Webform messsage from: ' . $this->name;
$from = 'webform#mail.com';
return $this
->from( $from )
->subject($subject)
->view('emails.contact-template');
}
}
Related
I have the following code that checks whether the API-key is the correct one before sending data to the front end.
file1Controller.php
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
class file1Controller extends AbstractController
{
/**
* #Route("/Some/URI", methods={"GET"}) // "/Some/URI" here
* #param Request $request
* #return JsonResponse
*/
public function list(Request $request)
{
if (empty($request->headers->get('api-key'))) {
return new JsonResponse(['error' => 'Please provide an API_key'], 401);
}
if ($request->headers->get('api-key') !== $_ENV['API_KEY']) {
return new JsonResponse(['error' => 'Invalid API key'], 401);
}
return new JsonResponse($this->getDoctrine()->getRepository('App:Something')->findAll()); //Something here
}
}
Which works excatly as intended (tested it with Postman) for my simple learning example. I would like to generalize it so that I can use it in other places. Almost everything should stay the same except the parts where there are comments. I have tried the following:
General.php
<?php
namespace App;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
class General extends AbstractController
{
private $request;
private $route;
private $entity;
/**
* ApiKeyAuthenticator constructor.
* #param Request $request
* #param String $route
* #param String $entity
*/
function __construct(Request $request, String $route, String $entity)
{
$this->request = $request;
$this->route = $route;
$this->entity = $entity;
}
/**
* #Route({$route}, methods={"GET"}) //notice here
* #return JsonResponse
*/
public function list()
{
if (empty($this->request->headers->get('api-key'))) {
return new JsonResponse(['error' => 'Please provide an API_key'], 401);
}
if ($this->request->headers->get('api-key') !== $_ENV['API_KEY']) {
return new JsonResponse(['error' => 'Invalid API key'], 401);
}
return new JsonResponse($this->getDoctrine()->getRepository('App:{$this->entity}')->findAll()); //notice here
}
}
Then I change the code of file1Controller.php to:
<?php
namespace App\Controller;
require(__DIR__.'/../General.php'); //note that there's no error accessing the file here
use Symfony\Component\HttpFoundation\Request;
class file1Controller
{
/**
* #param Request $request
*/
public function AuthenticateAPI(Request $request)
{
$AuthenticatorObject = new ApiKeyAuthenticator($request, "/Some/URI", 'Something'); //getting undefiend class
return $AuthenticatorObject;
}
}
This is unfortunately not working when testing it with Postman and I'm getting an undefiend class error on this line $AuthenticatorObject = new ApiKeyAuthenticator($request, "/Some/URI", 'Something'); in file1Controller.php
What did I do wrong and how could I fix it?
You shouldn't call your controllers like this in Symfony:
require(__DIR__.'/../General.php'); //note that there's no error accessing the file here
Please check out defining and accessing controllers as service in Symfony documentation:
How to Define Controllers as Services
How to Forward Requests to another Controller
I have a Task entity, with two mandatory, non-nullable, fields:
title
dueDatetime
and Form to create task. The form is called by external scripts through POST with application/x-www-form-urlencoded (so no json or anything fancy), so I use standard symfony to handle this.
Problem is I don't control the scripts, and if the script forgot one of the argument, symfony4 will directly throw an exception at the handleRequest step, before I have the time to check if the form is valid or not. Which result in an ugly response 500.
My question: How to avoid that ? The best for me would be to just continue to use "form->isValid()" as before , but if there's an other standard way to handle that, it's okay too.
Note: it would be best if I don't have to put my entity's setter as accepting null values
The exception I got:
Expected argument of type "DateTimeInterface", "NULL" given.
in vendor/symfony/property-acces /PropertyAccessor.php::throwInvalidArgumentException (line 153)
in vendor/symfony/form/Extension/Core/DataMapper/PropertyPathMapper.php->setValue (line 85)
in vendor/symfony/form/Form.php->mapFormsToData (line 622)
in vendor/symfony/form/Extension/HttpFoundation/HttpFoundationRequestHandler.php->submit (line 108)
in vendor/symfony/form/Form.php->handleRequest (line 492)
A curl that reproduce the error :
curl -d 'title=foo' http://127.0.0.1:8080/users/api/tasks
The code :
Entity:
class Task
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="bigint")
*/
private $id;
/**
* #Assert\NotNull()
* #Assert\NotBlank()
* #ORM\Column(type="string", length=500)
*/
private $title;
/**
*
* #ORM\Column(type="datetimetz")
*/
private $dueDatetime;
public function getDueDatetime(): ?\DateTimeInterface
{
return $this->dueDatetime;
}
public function setDueDatetime(\DateTimeInterface $dueDatetime): self
{
$this->dueDatetime = $dueDatetime;
return $this;
}
public function setTitle($title)
{
$this->title = $title;
return $this;
}
public function getTitle()
{
return $this->title;
}
}
Form
class TaskType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title')
->add('dueDatetime')
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(['data_class' => Task::class]);
}
}
Controller:
class TaskController extends AbstractController
{
/**
* #Route(
* "/users/api/tasks",
* methods={"POST"},
* name="user_api_create_task"
* )
*/
public function apiCreateTask(Request $request)
{
$task = new Task();;
// the use of createNamed with an empty string is just so that
// the external scripts don't have to know about symfony's convention
$formFactory = $this->container->get('form.factory');
$form = $formFactory->createNamed(
'',
TaskType::class,
$task
);
$form->handleRequest($request); // <-- this throw exception
// but this code should handle this no ?
if (!$form->isSubmitted() || !$form->isValid()) {
return new JsonResponse([], 422);
}
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($task);
$entityManager->flush();
return new JsonResponse();
}
}
There are at least 2 ways to handle this.
In the two ways you will have to add #Assert\NotNull() to the dueDatetime attribute.
1 - You can try/catch the exception of the handleRequest call.[edit] this one breaks the flow, not good.
2 - You can make nullable the setter setDueDatetime(\DateTimeInterface $dueDatetime = null). If you choose this one, please be sure to always validate your entity before an Insert/Update in DB else you will get an SQL error.
In the two cases it will be handled by the validator isValid() and you will have a nice error in your front end.
You need to allow nullable parameter (with "?") in method setDueDatetime
public function setDueDatetime(?\DateTimeInterface $dueDatetime): self
{
$this->dueDatetime = $dueDatetime;
return $this;
}
We have a legacy app which is not based on symfony. Doctrine is in use and now we would like to add validation to the models. Seems that the Annotations never get autoloaded, even when "use" statements are in use.
[Semantical Error] The annotation "#Symfony\Component\Validator\Constraints\NotBlank" in property Test\Stackoverflow\User::$Username does not exist, or could not be auto-loaded.
Wrote a small demo application to showcase the problem and how we create the entity manager and validation instance.
composer.json:
{
"require": {
"symfony/validator" : "~3.1"
, "doctrine/orm" : "~2.6.1"
}
}
index.php
require_once ('vendor/autoload.php');
// Load Entities, would normally be done over composer since they reside in a package
require_once('test/User.php');
require_once('MyAnnotationTestApp.php');
// create test app
$app = new MyAnnotationsTestApp();
$app->initEntityManager('localhost', 'annotation_test', 'root', 'mysql', 3306);
if(key_exists('test', $_GET)){
// Create entity and validate it
$entity = new \Test\Stackoverflow\User();
$entity->setUsername('StackoverflowUser');
if($app->testAnnotationWithoutLoading($entity)){
print "Seems the validation was working without preloading the asserts\n<br>";
}
if($app->testAnnotationWithLoading($entity)){
print "Seems the validation was working because we loaded the required class ourself.\n<br>";
}
print "\n<br><br>The question is why the required annotation classes never get autoloaded?";
}else{
// Load the validator class otherwise the annotation throws an exception
$notBlankValidator = new \Symfony\Component\Validator\Constraints\NotBlank();
print "We have cerated the tables but also had to load the validator class ourself.\n<br>\n<br>";
// create tables and
$app->updateDatabaseSchema();
print sprintf('Now lets run the test', $_SERVER['REQUEST_URI']);
}
Doctrine user Entity
<?php
namespace Test\Stackoverflow;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity()
* #ORM\Table(name="users")
*
*/
class User{
/**
* #ORM\Id
* #ORM\Column(name="Id",type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $Id;
public function getId(){
return $this->Id;
}
/**
* #ORM\Column(type="text", length=80, nullable=false)
* #Assert\NotBlank()
*/
protected $Username;
/**
* #return string
*/
public function getUsername()
{
return $this->Username;
}
/**
* #param string $Username
*/
public function setUsername($Username)
{
$this->Username = $Username;
}
}
Demo App with doctrine/validator initialisation:
<?php
final class MyAnnotationsTestApp {
/**
* #var \Doctrine\ORM\EntityManager
*/
private $entityManager;
/**
* #param string $host
* #param string $database
* #param string $username
* #param string $password
* #param integer $port
* #param array $options
* #return \Doctrine\ORM\EntityManager
*/
public function initEntityManager($host, $database, $username, $password, $port, array $options=null){
if($this->entityManager){
return $this->entityManager;
}
$connectionString = sprintf('mysql://%3$s:%4$s#%1$s/%2$s', $host, $database, $username, $password, $port);
$isDevMode = true;
$dbParams = array(
'url' => $connectionString
, 'driver' => 'pdo_mysql'
, 'driverOptions' => array(
1002 => "SET NAMES utf8mb4"
)
);
$cacheDriver = null;
$config = \Doctrine\ORM\Tools\Setup::createAnnotationMetadataConfiguration(array(), $isDevMode, '.cache/', $cacheDriver, false);
if($cacheDriver){
$config->setMetadataCacheImpl($cacheDriver);
$config->setQueryCacheImpl($cacheDriver);
$config->setResultCacheImpl($cacheDriver);
}
$this->entityManager = \Doctrine\ORM\EntityManager::create($dbParams, $config);
return $this->entityManager;
}
/**
* #return \Doctrine\ORM\EntityManager
*/
public function getEntityManager(){
return $this->entityManager;
}
public function updateDatabaseSchema(){
$metaData = array();
$usedEntities = array(
'Test\Stackoverflow\User'
);
foreach($usedEntities as $entity){
$metaData[] = $this->entityManager->getClassMetadata($entity);
}
$tool = new \Doctrine\ORM\Tools\SchemaTool($this->entityManager);
$tool->updateSchema($metaData);
$this->generateProxies($metaData);
}
/**
* Generate all the proxy classes for orm in the correct directory.
* Proxy dir can be configured over application configuration
*
*
* #throws \Exception
*/
final public function generateProxies($metaData)
{
$em = $this->getEntityManager();
$destPath = $em->getConfiguration()->getProxyDir();
if (!is_dir($destPath)) {
mkdir($destPath, 0777, true);
}
$destPath = realpath($destPath);
if (!file_exists($destPath)) {
throw new \Exception("Proxy destination directory could not be created " . $em->getConfiguration()->getProxyDir());
}
if (!is_writable($destPath)) {
throw new \Exception(
sprintf("Proxies destination directory '<info>%s</info>' does not have write permissions.", $destPath)
);
}
if (count($metaData)) {
// Generating Proxies
$em->getProxyFactory()->generateProxyClasses($metaData, $destPath);
}
}
/**
* #var \Symfony\Component\Validator\Validator\ValidatorInterface
*/
protected $validator;
/**
* #return \Symfony\Component\Validator\Validator\ValidatorInterface
*/
final protected function getValidator(){
if($this->validator){
return $this->validator;
}
$this->validator = \Symfony\Component\Validator\Validation::createValidatorBuilder()
->enableAnnotationMapping()
->getValidator();
return $this->validator;
}
/**
* #param \Test\Stackoverflow\User $entity
* #return bool
*/
final public function testAnnotationWithoutLoading(\Test\Stackoverflow\User $entity){
try {
print "test to validate the entity without preloading the Assert classes\n<br>";
$this->getValidator()->validate($entity);
return true;
} catch(\Exception $e){
print "<strong>Does not work since the Asserts classes never get loaded: </strong> Exception-message: ".$e->getMessage()."\n<br>";
return false;
}
}
/**
* #param \Test\Stackoverflow\User $entity
* #return bool
*/
final public function testAnnotationWithLoading(\Test\Stackoverflow\User $entity){
// Here we force the autoloader to require the class
$notBlankValidator = new \Symfony\Component\Validator\Constraints\NotBlank();
try {
print "Loaded the validator manually, will test of it fails now\n<br>";
$this->getValidator()->validate($entity);
return true;
} catch(\Exception $e){
print "<strong>Was not working: </strong> Exception-message: ".$e->getMessage()."\n<br>";
print sprintf("<strong>Even when we autoload the class it is not working. Type of assert: %s</strong>\n<br>", get_class($notBlankValidator));
return false;
}
}
}
If you are using the Symfony Standard Edition, you must update your
autoload.php file by adding the following code [1]
How are these annotations loaded? From looking at the code you could
guess that the ORM Mapping, Assert Validation and the fully qualified
annotation can just be loaded using the defined PHP autoloaders. This
is not the case however: For error handling reasons every check for
class existence inside the AnnotationReader sets the second parameter
$autoload of class_exists($name, $autoload) to false. To work
flawlessly the AnnotationReader requires silent autoloaders which many
autoloaders are not. Silent autoloading is NOT part of the PSR-0
specification for autoloading. [2]
// at the top of the file
use Doctrine\Common\Annotations\AnnotationRegistry;
// at the end of the file
AnnotationRegistry::registerLoader(function($class) use ($loader) {
$loader->loadClass($class);
return class_exists($class, false);
});
[1] https://symfony.com/blog/symfony2-2-0-rc4-released
[2] https://www.doctrine-project.org/projects/doctrine-annotations/en/1.6/annotations.html
I would like to change the default behaviour of the #Template annotation which automatically renders the template named as the controller action.
So in an ArticleController.php
/**
* #Route("/new", name="article_new")
* #Method("GET")
* #Template()
*/
public function newAction()
{
// ...
return array();
}
would render Article/new.html.twig.
I want to change this to referr to the name of the route the action was called with so you could have multiple routes for an action each rendering a different template.
This is the way I currently do it (without #Template):
/**
* #Route("/new", name="article_new")
* #Route("/new_ajax", name="article_new_ajax")
* #Method("GET")
*/
public function newAction()
{
// ...
$request = $this->getRequest();
$route = $request->attributes->get('_route');
$template = 'AcmeDemoBundle:' . $route . '.html.twig';
return $this->render($template, array(
// ...
));
}
I wonder now if there is a way to change the behaviour of #Template to do exactly that. Is there a way to customize the annotations or just some aproach to make it more automated?
Any ideas?
I have now found a solution using the kernelView event. This is independet of the #Template annotation. The kernelView event fires whenever a controller action doesn't return a response object.
(This solution is based on Symfony 2.4)
event listener service:
services:
kernel.listener.route_view:
class: Acme\DemoBundle\Templating\RouteView
arguments: ["#request_stack", "#templating"]
tags:
- { name: kernel.event_listener, event: kernel.view }
event listener class:
namespace Acme\DemoBundle\Templating;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
class RouteView
{
protected $controller;
protected $route;
protected $templating;
function __construct(RequestStack $requestStack, $templating)
{
$this->controller = $requestStack->getCurrentRequest()->attributes->get('_controller');
$this->route = $requestStack->getCurrentRequest()->attributes->get('_route');
$this->templating = $templating;
}
public function onKernelView(GetResponseForControllerResultEvent $event)
{
$controllerAction = substr($this->controller, strrpos($this->controller, '\\') + 1);
$controller = str_replace('Controller', '', substr($controllerAction, 0, strpos($controllerAction, '::')));
$template = 'AcmeDemoBundle:' . $controller . ':' . str_replace(strtolower($controller) . '_', '', $this->route) . '.html.twig';
$response = $this->templating->renderResponse($template, $event->getControllerResult());
$event->setResponse($response);
}
}
Now the controller behaves like this:
/**
* #Route("/new", name="article_new") -> Article:new.html.twig
* #Route("/new_ajax", name="article_new_ajax") -> Article:new_ajax.html.twig
* #Method("GET")
*/
public function newAction()
{
// ...
return array();
}
FOSRestBundle includes similar functionality to #Template but on class-level since my pull request if you use the #View annotation on class-level.
This can be useful if want to your template-filenames to reflect the action-names but not the route-names ( as opposed to what was asked for in the question ).
The rendered template will be i.e. ...
<controller-name>/<action-name>.html.twig
... for HTML views.
Example: AcmeBundle\Controller\PersonController::create() will render
AcmeBundle/Resources/views/Person/create.html.twig
Before the PR you had to annotate every method.
Annotating a method still gives the possibility to override template,template-variable and status-code though.
example:
/**
* #FOSRest\View(templateVar="testdata", statusCode=201)
*/
class PersonController implements ClassResourceInterface
{
public function newAction()
{
return $this->formHandler->createForm();
// template: Person/new.html.twig
// template variable is 'form'
// http status: 201
}
public function helloAction()
{
return "hello";
// template: Person/hello.html.twig
// template variable 'testdata'
// http status: 201
}
/**
* #FOSRest\View("AnotherBundle:Person:get", templatevar="person")
*/
public function getAction(Person $person)
{
return $person;
// template: AnotherBundle:Person:get
// template variable is 'person'
// http status: 201
}
/**
* #FOSRest\View("AnotherBundle:Person:overview", templatevar="persons", statusCode=200)
*/
public function cgetAction()
{
return $this->personManager->findAll();
// template: AnotherBundle:Person:overview
// template variable is 'persons'
// http status: 200
}
// ...
I'm trying to get this simple notification service working and I am having no joy at all. I've never used services in symfony before so I could be overlooking something pretty basic, however it all seems correct to me so I'm kind of banging my head against a wall here.
I've included everything to do with the service, help would be really appreciated!
Stack Trace:
[1] Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException: You have requested a non-existent service "game.notify".
at n/a
in D:\web\www\mygame\app\bootstrap.php.cache line 1968
at Symfony\Component\DependencyInjection\Container->get('game.notify')
in D:\web\www\mygame\vendor\symfony\symfony\src\Symfony\Bundle\FrameworkBundle\Controller\Controller.php line 252
at Symfony\Bundle\FrameworkBundle\Controller\Controller->get('game.notify')
in D:\web\www\mygame\src\Game\MainBundle\Controller\PageController.php line 10
at Game\MainBundle\Controller\PageController->indexAction()
in line
at call_user_func_array(array(object(PageController), 'indexAction'), array())
in D:\web\www\mygame\app\bootstrap.php.cache line 2843
at Symfony\Component\HttpKernel\HttpKernel->handleRaw(object(Request), '1')
in D:\web\www\mygame\app\bootstrap.php.cache line 2817
at Symfony\Component\HttpKernel\HttpKernel->handle(object(Request), '1', true)
in D:\web\www\mygame\app\bootstrap.php.cache line 2946
at Symfony\Component\HttpKernel\DependencyInjection\ContainerAwareHttpKernel->handle(object(Request), '1', true)
in D:\web\www\mygame\app\bootstrap.php.cache line 2248
at Symfony\Component\HttpKernel\Kernel->handle(object(Request))
in D:\web\www\mygame\web\app_dev.php line 28
Notify Controller:
Located at: Game/MainBundle/Controller/NotifyController.php
<?php
namespace Game\MainBundle\Controller;
class NotifyController
{
private $defaults
= array(
"type" => "flash",
),
$flashes = array();
/**
* #param \Symfony\Component\HttpFoundation\Session\Session $session
*/
public function __construct(\Symfony\Component\HttpFoundation\Session\Session $session)
{
$this->session = $session;
}
/**
* Depending on the supplied type argument, add the values
* to the session flashBag or $this->flashes
*
* #param string $name
* #param array $arguments
*/
public function add($name, array $arguments = array())
{
$arguments += $this->defaults;
// If the type is flash then add the values to the session flashBag
if ($arguments["type"] === "flash") {
$this->session->getFlashBag()->add($name, $arguments);
}
// Otherwise if its instant then add them to the class variable $flashes
elseif ($arguments["type"] === "instant") {
// We want to be able to have multiple notifications of the same name i.e "success"
// so we need to add each new set of arguments into an array not overwrite the last
// "success" value set
if (!isset($this->flashes[$name])) {
$this->flashes[$name] = array();
}
$this->flashes[$name][] = $arguments;
}
}
/**
* Check the flashBag and $this->flashes for existence of $name
*
* #param $name
*
* #return bool
*/
public function has($name)
{
if($this->session->getFlashBag()->has($name)){
return true;
} else {
return isset($this->flashes[$name]);
}
}
/**
* Search for a specific notification and return matches from flashBag and $this->flashes
*
* #param $name
*
* #return array
*/
public function get($name)
{
if($this->session->getFlashBag()->has($name) && isset($this->flashes[$name])){
return array_merge_recursive($this->session->getFlashBag()->get($name), $this->flashes[$name]);
} elseif($this->session->getFlashBag()->has($name)) {
return $this->session->getFlashBag()->get($name);
} else {
return $this->flashes[$name];
}
}
/**
* Merge all flashBag and $this->flashes values and return the array
*
* #return array
*/
public function all()
{
return array_merge_recursive($this->session->getFlashBag()->all(), $this->flashes);
}
}
NotifyExtension.php
Located at: Game/MainBundle/DependencyInjection/NotifyExtension.php
<?php
namespace Game\MainBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;
/**
* This is the class that loads and manages your bundle configuration
*
* To learn more see {#link http://symfony.com/doc/current/cookbook/bundles/extension.html}
*/
class NotifyExtension extends Extension
{
/**
* {#inheritDoc}
*/
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
}
}
Configuration.php
Located at: Game/MainBundle/DependencyInjection/Configuration.php
<?php
namespace Game\MainBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
/**
* This is the class that validates and merges configuration from your app/config files
*
* To learn more see {#link http://symfony.com/doc/current/cookbook/bundles/extension.html#cookbook-bundles-extension-config-class}
*/
class Configuration implements ConfigurationInterface
{
/**
* {#inheritDoc}
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('game_main');
// Here you should define the parameters that are allowed to
// configure your bundle. See the documentation linked above for
// more information on that topic.
return $treeBuilder;
}
}
Services.yml
Located at: Game/MainBundle/Resources/Config/services.yml
parameters:
game.notify.class: Game\MainBundle\Controller\NotifyController
services:
game.notify:
class: "%game.notify.class%"
arguments:
session: #session
PageController.php
Located at: Game/MainBundle/Controller/PageController.php
<?php
namespace Game\MainBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class PageController extends Controller
{
public function indexAction()
{
$notify = $this->get("game.notify");
$notify->add("test", array("type" => "instant", "message" => "This is awesome"));
if ($notify->has("test")) {
return array("notifications" => $notify->get("test"));
}
return $this->render('GameMainBundle:Page:index.html.twig');
}
}
Based on your answer to my first comment, it would appear that your services are never being loaded due to not following the naming convention for your extension class.
If you have a GameMainBundle for your bundle then you should have GameMainExtension for your extension.
More info here: http://symfony.com/doc/current/cookbook/bundles/best_practices.html
You might still have some problems once you get services.yml loaded. Calling your service a controller is a bit non-standard. But see what happens.