I keep rereading the symfony 4 documentation to try to generate twig template with the console commands but I did not find a command . Is there any one know a bundle to generate twig template with console commands ?
I'm solved
class SendEmailNotify extends Command
{
private $container;
private $twig;
private $mailer;
public function __construct($name = null, \Psr\Container\ContainerInterface $container, \Swift_Mailer $mailer)
{
parent::__construct($name);
$this->container = $container;
$this->twig = $this->container->get('twig');
$this->mailer = $mailer;
}
I checked it and the sad answer is no. The list of maker commands is available here. You can even make a twig extension, but not a view. It is worth submitting to them I think.
I needed Twig functionality to send emails from my custom console command.
This is the solution I came up with.
First I installed Twig.
composer require "twig/twig:^2.0"
Then created my own twig service.
<?php
# src/Service/Twig.php
namespace App\Service;
use Symfony\Component\HttpKernel\KernelInterface;
class Twig extends \Twig_Environment {
public function __construct(KernelInterface $kernel) {
$loader = new \Twig_Loader_Filesystem($kernel->getProjectDir());
parent::__construct($loader);
}
}
Now my email command looks like this.
<?php
# src/Command/EmailCommand.php
namespace App\Command;
use Symfony\Component\Console\Command\Command,
Symfony\Component\Console\Input\InputInterface,
Symfony\Component\Console\Output\OutputInterface,
App\Service\Twig;
class EmailCommand extends Command {
protected static $defaultName = 'mybot:email';
private $mailer,
$twig;
public function __construct(\Swift_Mailer $mailer, Twig $twig) {
$this->mailer = $mailer;
$this->twig = $twig;
parent::__construct();
}
protected function configure() {
$this->setDescription('Email bot.');
}
protected function execute(InputInterface $input, OutputInterface $output) {
$template = $this->twig->load('templates/email.html.twig');
$message = (new \Swift_Message('Hello Email'))
->setFrom('emailbot#domain.com')
->setTo('someone#somewhere.com')
->setBody(
$template->render(['name' => 'Fabien']),
'text/html'
);
$this->mailer->send($message);
}
}
This instruction will make a Controller without a twig template
php bin/console make:controller --no-template
Surprisingly this instruction will make a Controller and a template file and subdirectory
php bin/console make:controller
Related
class Counter extends Component
{
public $counter = 0;
public $name;
public function mount($name)
{
$this->name = $name;
}
public function render()
{
return view('livewire.counter');
}
}
I'm trying to pass the name variable as parameter to the livewire component call like so #livewire('counter', ['name' => $name]) but receiving "Undefined variable $name" error. I have been following the livewire docs too, still same error. what could possibly be wrong? Thank you.
If $name is not defined, then it is not a property available in the blade file where you are including Livewire. Since we don't know anything about your blade file or controllers, I can't give you a direct answer. However, you should check if $name is set anywhere and if so, if it's available in your blade view. This error is not related to Livewire. If you've simply copied the docs without checking the variables, you might as well set it right away:
#livewire('counter', ['name' => 'Gilles'])
Maybe I am missing something... doh, I think so, but could not find an answer to that.
WebTestCase generates this constructor sample:
public function __construct(?string $name = null, array $data = [], string $dataName = '')
{
parent::__construct($name, $data, $dataName);
}
Was trying to add my service as the first or last argument - Symfony throws an error:
Type error: Too few arguments to function Tests\AppBundle\Manager\ContactManagerTest::__construct(), 0 passed in /Library/WebServer/Documents/HEPT/vendor/bin/.phpunit/phpunit-5.7/src/Framework/TestSuite.php on line 568 and at least 1 expected in /Library/WebServer/Documents/HEPT/tests/AppBundle/Manager/ContactManagerTest.php:22
Should I somehow use container directly? Why is autowiring not working for WebTestCase classes if there is a bridge class?
WebTestCase are used in the context of PHPUnit (which has nothing to do with Symfony and its dependency injection).
They actually generate the kernel and its container, see this piece of code extracted from Symfony source code:
protected static function createClient(array $options = array(), array $server = array())
{
$kernel = static::bootKernel($options);
$client = $kernel->getContainer()->get('test.client');
$client->setServerParameters($server);
return $client;
}
This means that you can easily access the container like this:
$kernel = static::bootKernel($options);
$container = $kernel->getContainer();
Please note also that static::$kernel->getContainer() is available as soon as you created your client to make your test.
How can I in Laravel 5 make global variable which will be available in all Blade templates?
Option 1:
You can use view::share() like so:
<?php namespace App\Http\Controllers;
use View;
//You can create a BaseController:
class BaseController extends Controller {
public $variable1 = "I am Data";
public function __construct() {
$variable2 = "I am Data 2";
View::share ( 'variable1', $this->variable1 );
View::share ( 'variable2', $variable2 );
View::share ( 'variable3', 'I am Data 3' );
View::share ( 'variable4', ['name'=>'Franky','address'=>'Mars'] );
}
}
class HomeController extends BaseController {
//if you have a constructor in other controllers you need call constructor of parent controller (i.e. BaseController) like so:
public function __construct(){
parent::__construct();
}
public function Index(){
//All variable will be available in views
return view('home');
}
}
Option 2:
Use a composer:
Create a composer file at app\Composers\HomeComposer.php
NB: create app\Composers if it does not exists
<?php namespace App\Composers;
class HomeComposer
{
public function compose($view)
{
//Add your variables
$view->with('variable1', 'I am Data')
->with('variable2', 'I am Data 2');
}
}
Then you can attached the composer to any view by doing this
<?php namespace App\Http\Controllers;
use View;
class HomeController extends Controller{
public function __construct(){
View::composers([
'App\Composers\HomeComposer' => ['home'] //attaches HomeComposer to home.blade.php
]);
}
public function Index(){
return view('home');
}
}
Option 3:
Add Composer to a Service Provider, In Laravel 5 I prefer having my composer in App\Providers\ViewServiceProvider
Create a composer file at app\Composers\HomeComposer.php
Add HomeComposer to App\Providers\ViewServiceProvider
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use View;
use App\Composers\HomeComposer;
use Illuminate\Support\Facades\Blade;
class ViewServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* #return void
*/
public function register()
{
//
}
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
//add to all views
view()->composer('*', HomeComposer::class);
//add to only home view
//view()->composer('home', HomeComposer::class);
}
}
Create a new Service Provider as suggested in here
Add your new Service Provider to the configuration file (config/app.php).
In the boot method of your new Service Provider use:
View::share( 'something_cool', 'this is a cool shared variable' );
Now you are ready to use $something_cool in all of your views.
Hope this helps.
Searching for solution of the same problem and found the best solution in Laravel documentation. Just use View::share in AppServiceProvider like this:
View::share('key', 'value');
Details here.
You can do this with view composers. View composers are executed when a template is loaded. You can pass in a Closure with additional functionality for that view. With view composers you can use wildcards. To make a view composer for every view just use a *.
View::composer('*', function($view)
{
$view->with('variable','Test value');
});
You can also do this without a closure as you can see in the docs.
View::composer('*', 'App\Http\ViewComposers\ProfileComposer');
The profile composer class must have a compose method.
View composers are executed when a view is rendered. Laravel has also view creators. These are executed when a view is instantiated.
You can also choose to use a BaseController with a setupLayout method. Then every view which you will load is loaded through the setupLayout method which adds some additional data. However, by using view composers you're pretty sure that the code is executed. But with the BaseController approach you've more flexibility because you can skip the loading of the extra data.
EDIT: As mentioned by Nic Gutierrez you can also use view share.
Also, you can do this in the Route.php file:
view()->share('variableName', $variable);
I would rather use middleware with the view() facade helper. (Laravel 5.x)
Middleware is easier to mantain and does not make a mess in the controllers class tree.
Steps
Create the Middleware
/app/Http/Middleware/TimezoneReset.php
To create a middleware you can run php artisan make:middleware GlobalTimeConfig
share() the data you need shared
<?php
namespace App\Http\Middleware;
use Closure;
class GlobalTimeConfig
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$time_settings = [
'company_timezone' => 'UTC',
'company_date_format' => 'Y-m-d H:i:s',
'display_time' => true,
];
view()->share('time_settings', $time_settings);
return $next($request);
}
}
Register the newly created middleware
Add the middleware to your middleware route group as per example below
/app/Http/Kernel.php
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\GlobalTimeConfig::class,
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
'throttle:60,1',
'bindings',
],
];
Access data from templates
Access the data from any template with the given key in the View::share() method call
eg.:
Company timezone: {{ $time_settings['company_timezone'] }}
EDIT:
Nic Gutierrez's Service Provider answer might be a better (or the best) solution.
and you can give array not just View::share('key', 'value');
can put array like View::share(['key'=>'value','key'=>'value'])
You can add in Controller.php file:
use App\Category;
And then:
class Controller extends BaseController {
public function __construct() {
$categories = Category::All();
\View::share('categories', $categories);
}
}
you can flash it into the session, you can define it in the .env file (static vars)
I've got a Doctrine Entity defined that maps to a View in my database. All works fine, the Entity relations work fine as expected.
Problem now is that when running orm:schema-manager:update on the CLI a table gets created for this entity which is something I want to prevent. There already is a view for this Entity, no need to create a table for it.
Can I annotate the Entity so that a table won't be created while still keeping access to all Entity related functionality (associations, ...)?
Based on the original alswer of ChrisR inspired in Marco Pivetta's post I'm adding here the solution if you're using Symfony2:
Looks like Symfony2 doesn't use the original Doctrine command at:
\Doctrine\ORM\Tools\Console\Command\SchemaTool\UpdateCommand
Instead it uses the one in the bundle:
\Doctrine\Bundle\DoctrineBundle\Command\Proxy\UpdateSchemaDoctrineCommand
So basically that is the class that must be extended, ending up in having:
src/Acme/CoreBundle/Command/DoctrineUpdateCommand.php:
<?php
namespace App\Command;
use Doctrine\Bundle\DoctrineBundle\Command\Proxy\UpdateSchemaDoctrineCommand;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Tools\SchemaTool;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class DoctrineUpdateCommand extends UpdateSchemaDoctrineCommand
{
protected function executeSchemaCommand(InputInterface $input, OutputInterface $output, SchemaTool $schemaTool, array $metadatas, SymfonyStyle $ui): ?int
{
$ignoredEntities = [
'App\Entity\EntityToIgnore',
];
$metadatas = array_filter($metadatas, static function (ClassMetadata $classMetadata) use ($ignoredEntities) {
return !in_array($classMetadata->getName(), $ignoredEntities, true);
});
return parent::executeSchemaCommand($input, $output, $schemaTool, $metadatas, $ui);
}
}
Eventually it was fairly simple, I just had to subclass the \Doctrine\ORM\Tools\Console\Command\SchemaTool\UpdateCommand into my own CLI Command. In that subclass filter the $metadatas array that's being passed to executeSchemaCommand() and then pass it on to the parent function.
Just attach this new subclassed command to the ConsoleApplication you are using in your doctrine cli script and done!
Below is the extended command, in production you'll probably want to fetch the $ignoredEntities property from you config or something, this should put you on the way.
<?php
use Doctrine\ORM\Tools\Console\Command\SchemaTool\UpdateCommand;
use Doctrine\ORM\Tools\SchemaTool;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class My_Doctrine_Tools_UpdateCommand extends UpdateCommand
{
protected $name = 'orm:schema-tool:myupdate';
protected $ignoredEntities = array(
'Entity\Asset\Name'
);
protected function executeSchemaCommand(InputInterface $input, OutputInterface $output, SchemaTool $schemaTool, array $metadatas, SymfonyStyle $ui)
{
/** #var $metadata \Doctrine\ORM\Mapping\ClassMetadata */
$newMetadata = [];
foreach ($metadatas as $metadata) {
if (!in_array($metadata->getName(), $this->ignoredEntities)) {
$newMetadata[] = $metadata;
}
}
return parent::executeSchemaCommand($input, $output, $schemaTool, $newMetadata, $ui);
}
}
PS: credits go to Marco Pivetta for putting me on the right track. https://groups.google.com/forum/?fromgroups=#!topic/doctrine-user/rwWXZ7faPsA
Quite old one but there is also worth nothing solution using Doctrine2: postGenerateSchema event listener - for me it's better than overriding
Doctrine classes:
namespace App\Doctrine\Listener;
use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs;
/**
* IgnoreTablesListener class
*/
class IgnoreTablesListener
{
private $ignoredTables = [
'table_name_to_ignore',
];
public function postGenerateSchema(GenerateSchemaEventArgs $args)
{
$schema = $args->getSchema();
$tableNames = $schema->getTableNames();
foreach ($tableNames as $tableName) {
if (in_array($tableName, $this->ignoredTables)) {
// remove table from schema
$schema->dropTable($tableName);
}
}
}
}
Also register listener:
# config/services.yaml
services:
ignore_tables_listener:
class: App\Doctrine\Listener\IgnoreTablesListener
tags:
- {name: doctrine.event_listener, event: postGenerateSchema }
No extra hooks is necessary.
In Doctrine 2.7.0 it was introduced the new SchemaIgnoreClasses entity manager config option that basically ignores the configured classes from any schema action.
To use it with Symfony we only need to add the schema_ignore_classes key in the Doctrine entity manager configuration like this:
doctrine:
dbal:
# your dbal configuration
orm:
default_entity_manager: default
entity_managers:
default:
connection: default
mappings:
Main:
is_bundle: false
type: annotation
dir: '%kernel.project_dir%/src/Entity/Main'
prefix: 'App\Entity\Main'
alias: Main
schema_ignore_classes:
- Reference\To\My\Class
- Reference\To\My\OtherClass
$schema->getTableNames() was not working (I don't know why).
So:
<?php
namespace AppBundle\EventListener;
use Doctrine\Bundle\DoctrineBundle\Command\Proxy\UpdateSchemaDoctrineCommand;
use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs;
class IgnoreTablesListener extends UpdateSchemaDoctrineCommand
{
private $ignoredEntities = [
'YourBundle\Entity\EntityYouWantToIgnore',
];
/**
* Remove ignored tables /entities from Schema
*
* #param GenerateSchemaEventArgs $args
*/
public function postGenerateSchema(GenerateSchemaEventArgs $args)
{
$schema = $args->getSchema();
$em = $args->getEntityManager();
$ignoredTables = [];
foreach ($this->ignoredEntities as $entityName) {
$ignoredTables[] = $em->getClassMetadata($entityName)->getTableName();
}
foreach ($schema->getTables() as $table) {
if (in_array($table->getName(), $ignoredTables, true)) {
// remove table from schema
$schema->dropTable($table->getName());
}
}
}
}
And Register a service
# config/services.yaml
services:
ignore_tables_listener:
class: AppBundle\EventListener\IgnoreTablesListener
tags:
- {name: doctrine.event_listener, event: postGenerateSchema }
Worked fine! ;)
If problem is only with producing errors in db_view, when calling doctrine:schema:update command, why not simplest way:
remove # from #ORM\Entity annotation
execute doctrine:schema:update
add # to ORM\Entity annotation
;-)
There is a cookbook for adding globals to the twig templating engine, but it doesn't get into doing the same thing for the php engine. How would I do this?
So I might have something like:
# config.yml
someSortOfReferenceToThePHPEngineInstance:
calls:
- [ addGlobals, ["foo", "bar"] ]
- [ addGlobals, ["myService", "#myService"] ]
And then access those like:
// templateName.contentType.php
<?
echo $foo; // echos "bar"
echo $myService->myMethod($foo); // echos the result of modifying "bar" with "myMethod" method of "myService" service
I could not find any documention on this for the PHP engine...
What does work however is:
Config:
//config.yml
parameters:
hello: "YO!"
PHP Template:
// index.html.php
<?php
print $view->container->parameters['hello'];
This does not fit as nicely as the twig convention... Maybe there is better way - I have not debugged any further...
Here are a couple of options:
If you create a base controller that all others inherit from, you can override symfony's render function and add keys to the parameters argument, like:
public function render($view, array $parameters = array(), Response $response = null){
if(!array_key_exists("bar", $parameters){
$parameters["foo"] = $this->get("foo");
}
if(!array_key_exists("bar", $parameters){
$parameters["bar"] = $this->get("bar");
}
return parent::render($view, $parameters, $response);
}
This is the only way I see to modify the "global" variables "globally", though they'll not be available in any views rendered by controllers you don't create (of course, those'll likely be done in Twig anyway and you can use the normal twig means of adding functionality).
The PHP rendering engine has what're called "helpers", which you can access via array keys of $view, like:
$view["foo"]->doSomething();
We created a class for easily making services into helpers:
use Symfony\Component\Templating\Helper\Helper as BaseHelper;
class Helper extends BaseHelper{
protected $name;
public $service;
public function __construct($name, $service){
$this->name = $name;
$this->service = $service;
}
public function __get($name){
if(isset($this->service->$name)){
return $this->service->$name;
}
}
public function __call($name, $arguments){
if(method_exists($this->service, $name)){
return call_user_func_array(array($this->service,$name), $arguments);
}
}
public function getName(){
return $this->name;
}
}
Then in our configuration under the services we'd add:
helper.foo:
class: %helper.class%
arguments:
name: "foo"
helper: "#foo"
tags:
- { name: templating.helper, alias: foo }
This would theoretically be available then to any view files, even those with controllers you don't have control of.
I had a very same problem. For some reason this feature is only available for Twig templating with TwigBundle. Both Twig and PHP templating engines provide possibility to define global variables, but only Twig engine has configuration for that. For me the only real way to achieve that is something you proposed in the question post - to define method calls (and this is the way Twig globals are registered).
Problem is, that with DI extensions you can't access service definition from outside your extension, so you can't add these calls from your DI extension. The way for me was to do that with DI compiler pass.
But I'm also developer of ChillDevViewHelpersBundle and since I was facing this problem in most of my projects I decided to implement it there for common use and you can use 0.1.8 release for this feature.