SonataAdminBundle new block type - symfony

Did anybody create new block type for SonataAdminBundle using extend bundle? I'm trying to do so but it only works if I add service directly in original block.xml. Anything change in block.xml under config directory in MyBundle (extended SonataAdminBundle) has no effect.
Any one has solution? Plz help!!!

Are you sure that your XML file is correctly loaded in your own bundle?
It must be done in the load() method declared in file MyBundle\DependencyInjection\MyBundleExtension.php:
$loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('block.xml');

Related

Symfony3 Docs WSSE fail "Cannot replace arguments if none have been configured yet"

Following this
http://symfony.com/doc/current/security/custom_authentication_provider.html
Results in
Service "security.authentication.provider.wsse.wsse_secured": Cannot replace arguments if none have been configured yet.
I cannot find anything about this error anywhere. This uses the doc's WSSE code and it fails.
This repo shows it failing https://github.com/jakenoble/wsse_test
I want to get it working eventually with the FOS User Bundle. But I cannot get it to work with a basic Symfony3 install so FOS User Bundle is out of the question at the moment.
Having dug around a bit...
There is a an arg at element index_0 on class Symfony\Component\DependencyInjection\ChildDefinition the object for the arg at element index_0 has an id of fos_user.user_provider.username_email.
The replace call then attempts to get the arguments of fos_user.user_provider.username_email, but there are none. Then the error occurs.
Any ideas?
TL;DR Move your service definitions below the autoloading definitions in services.yml and change the WsseFactory code to
$container
->setDefinition($providerId, new ChildDefinition(WsseProvider::class))
->setArgument('$userProvider', new Reference($userProvider))
;
Full explanation.
The first mistake in the supplied code is that the services definitions prepend the autoloading lines. The autoloading will just override the previous definitions and will cause the AuthenticationManagerInterface failure. Moving the definitions below will fix the issue. The other way to fix the issue is aliases as #yceruto and #gintko pointed.
But only that move will not make the code work despite on your answer. You probably didnt notice how changed something else to make it work.
The second issue, the failure of replaceArgument, is related to Symfony's order of the container compilation as was correctly supposed. The order is defined in the PassConfig class:
$this->optimizationPasses = array(array(
new ExtensionCompilerPass(),
new ResolveDefinitionTemplatesPass(),
...
$autowirePass = new AutowirePass(false),
...
));
I omitted the irrelevant passes. The security.authentication.provider.wsse.wsse_secured definition created by the WsseFactory is produced first. Then ResolveDefinitionTemplatesPass will take place, and will try to replace the arguments of the definition and raise the Cannot replace arguments exception you got:
foreach ($definition->getArguments() as $k => $v) {
if (is_numeric($k)) {
$def->addArgument($v);
} elseif (0 === strpos($k, 'index_')) {
$def->replaceArgument((int) substr($k, strlen('index_')), $v);
} else {
$def->setArgument($k, $v);
}
}
The issue will appear cause the pass will call Definition::replaceArgument for index_0. As the parent definition doesn't have an argument at position 0 neither in the former services.xml nor in the fixed one. AutowirePass wasn't executed yet, the autogenerated definitions has no arguments, the manual definition has the named $cachePool argument only.
So to fix the issue you could use rather:
->setArgument(0, new Reference($userProvider)); //proposed by #yceruto
->replaceArgument('$userProvider', new Reference($userProvider)); //proposed by #gintko
->setArgument('$userProvider', new Reference($userProvider)); // by me
All of them will entail the calls of Definition::addArgument or Definition::setArgument and will work out. There are only the little difference:
- setArgument(0, ... could not work for some other scenarios;
- I like ->setArgument('$userProvider' more than ->replaceArgument('$userProvider' due to semantics. Nothing to replace yet!
Hope the details why the issue appears now clear.
PS. There are also few other funny ways to overcome the issue.
Fix the config a bit:
AppBundle\Security\Authentication\Provider\WsseProvider:
arguments:
0: ''
$cachePool: '#cache.app'
public: false
Or set an alias of Symfony\Component\Security\Core\User\UserProviderInterface to let the autowire do the rest for you.
$container
->setDefinition($providerId, new ChildDefinition(WsseProvider::class))
// ->replaceArgument(0, new Reference($userProvider))
;
$container
->setAlias('Symfony\Component\Security\Core\User\UserProviderInterface',$userProvider)
;
This look like at this moment the provider definition doesn't have the autowired arguments ready, maybe related to the order in which the "CompilerPass" are processed, by now you can solve it with these little tweaks:
change this line in WsseFactory.php:
->replaceArgument(0, new Reference($userProvider))
by:
->setArgument(0, new Reference($userProvider))
and add this alias to services.yml to complete the autowired arguments of the new provider:
Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface: '#security.authentication.manager'
EDIT: (deleted the previous contents due to the comment)
I see you don't use FOSUserBundle that makes things more comples. I did not yet create PR.
I think you are missing custom user entity. (see the link I have provided to create your own entity - my PR would be similar to these lines, if you are unable to create your own entity using my description and the link I'll provide PR, but that will take longer).
The steps you have to take:
Create your own User entity via src/AppBundle/Entity/User.php
Create DB table via php bin/console doctrine:schema:update --force
Configure Security to load your Entity - use AppBundle\Entity\User;
Then you have to create your own user. This can be tricky see password encoding for more.
(optional) Forbid interactive users If you want to login using
username OR email you can create Custom Query to Load the User
These steps should be enough for you to create your own user Entity and you don't have to use the FOSUserBundle.
EDIT:
Ok, so read some source code, and in ChildDefinition:100 you can see, that arguments are also indexed by argument's $name. So, it must be, that at compile time arguments of autowired services are passed with their named indexes, instead of numbered indexes.
WsseFactory.php
->replaceArgument(0, new Reference($userProvider));
so argument should be referenced by it's name:
->replaceArgument('$userProvider', new Reference($userProvider));
After this change, you will get new exception error, which says that it's not possible to autowire $authenticationManager in WsseListener service by it's interface. You can fix it simply by specifying alias for that interface in your services.yml:
Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface: '#security.authentication.manager'
I guess this issue is related with new autowire feature, I will try to investigate it later why it is so.
For now, you can use old way to define services:
services.yml:
app.security.wsse_provider:
class: AppBundle\Security\Authentication\Provider\WsseProvider
arguments:
- ''
- '#cache.app'
public: false
app.security.wsse_listener:
class: AppBundle\Security\Firewall\WsseListener
arguments: ['#security.token_storage', '#security.authentication.manager']
public: false
in WsseFactory.php, following lines:
new ChildDefinition(WsseFactory::class);
new ChildDefinition(WsseListener::class);
translates into:
new ChildDefinition('app.security.wsse_provider');
new ChildDefinition('app.security.wsse_listener');
It seems the issue is because my config in services.yml came before the new auto wiring stuff.
So simply moving it below the auto wiring fixes it.

autoload bundles that are in a specific folder

Due to some set up restraints I would like to autoload bundles that are in a specific folder without adding new bundles in the AppKernel.php.
For example:
I have a folder called autoload in /src/.
I would like any bundle I create in there to automatically load without needing to register them in the AppKernel.php.
My idea is to write a function to loop through all the folders within /src/autoload and build the string for the bundle and append it to $bundles in registerBundles().
//loop through src/autoload
//Create the string ... new Namespace/Autoload/BundleName()
//$bundles[] = $listOfBundlesFromAutoload
I know this is not conventional and if there is no other way then I will just add the new bundles manually but there are some constraints that will make it difficult to keep on registering new bundles every time a new bundle is added.
Any ideas?
Sure you can, as long as you know it's not conventional and wash your hands well afterwards :-)
Inside your AppKernel.php, just after the default bundles are loaded you can put:
$bundleFiles = glob(__DIR__ . '/../src/*Bundle/*Bundle.php');
foreach ($bundleFiles as $bundleFile) {
$className = $this->getClassFullNameFromFile($bundleFile);
$bundles[] = new $className();
}
Where the getClassFullNameFromFile was copied as it is from this answer.

Symfony2 bundle specific Twig configuration/variables

I know that I can set Twig variables in my app/config/config.yml, however I want to set variables on a per bundle level (eg: Bundle/Resources/config/??.yml).
For example I want to include a bundle version identifier in the footer of my pages. I tried placing twig config into my bundles' services.yml however Symfony wasn't able to parse the configuration.
How can I achieve this?
I'm not sure how you could implement bundle specific configs for your example. The configs in bundles tend to be imported into the main config files which are now environment specific rather than bundle specific.
However, for your example I would just make a twig extension which returns the name of the bundle you're using. That way you can use it wherever you like in your templates. You can get the fully named route of your controller from the request, then just use preg matching to get the Bundle name. Something like the below should work:
public function getBundleName()
{
$pattern = "#([a-zA-Z]*)Bundle#";
$matches = array();
preg_match($pattern, $this->container->get('request')->get('_controller'), $matches);
return $matches[1];
}
In this example $this->container has been set in the constructor to be an instance of the container. If you are using another method to get the controller then substitute accordingly.

How to add a personal vendor to a Symfony 2 project?

I am new with Symfony 2. I would like to add classes like libraries in my project but I dont know how.
Suppose I have 2 classes A and B.
I located them at this position : vendor/my_vendor_name/xxxx/A.php and vendor/my_vendor_name/xxxx/B.php
in each classes I defined the same namespace :
namespace my_vendor_name/xxxx
Now I would like to use those 2 classes in my bundles by calling :
use my_vendor_name/xxxx/A or my_vendor_name/xxxx/B
But It is like my namespaces do not exist (class not found)... maybe I have to declare those namespaces somewhere, to register them.
My app/autoload.php
<?php
use Doctrine\Common\Annotations\AnnotationRegistry;
$loader = require __DIR__.'/../vendor/autoload.php';
// intl
if (!function_exists('intl_get_error_code')) {
require_once __DIR__.'/../vendor/symfony/symfony/src/Symfony/Component/Locale/Resources/stubs/functions.php';
$loader->add('', __DIR__.'/../vendor/symfony/symfony/src/Symfony/Component/Locale/Resources/stubs');
}
AnnotationRegistry::registerLoader(array($loader, 'loadClass'));
return $loader;
This is not the correct way to procede.
You have, in this order:
Create a bundle under src/ directory
Register your bundle into app/AppKernel.php file
Create your classes under your bundle
Use those classes wherever you want
Real example:
Bundle Creation
Create a directory under src/ dir. This dir wll contains all your bundles. By convention, this directory should have to be your "company" name (for our example, say it is Foo).
After that, create a directory for your bundle, with name of your bundle. For our example. we decide to call it FooBundle.
So, you'll have this directory tree: src/Foo/FooBundle/
Into FooBundle/ you have to create a file that is used for bundle registration. This file is reported here:
<?php
namespace Foo\FooBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class FooBundle extends Bundle
{
}
File name is FooFooBundle.php
Register your bundle
Go into app/AppKernel.php file and add this line
public function registerBundles()
{
[...]
$bundles = array(
[...]
new Foo\FooBundle\FooFooBundle());
[...]
Congratulation! Your bundle is now registered!
Create your classes
Simply create your classes into your bundle. I suggest you to pack them into a directory like Core or whatever you want
Use your classes
Suppose that you have defined a class called A, into your brand new bundle.
Use it is quite simple:
use Foo\FooBundle\Core\A
into the file you want to use it
All should work now
Little Note:
A bundle inserted into src is different from a bundle inserted into vendor one only because your bundle (src one) is used by you, into your project only. A vendor bundle is someone else bundle that you use into your project. Behaviours (how to create bundle, how to register and use it) are exactly the same.
Little Note 2:
If you have to use external library or "old" php plain classes, Ahmed answer gives you a good point of start
Take a deeper look at the documentation. It's well explained in the The ClassLoader Component section.
From the documentation,
If the classes to autoload use namespaces, use the registerNamespace() or registerNamespaces() methods.
For classes that follow the PEAR naming convention, use the registerPrefix() or registerPrefixes() methods.
Note, The documentation is full of examples that fit your needs (Adding external classes or PHP libraries to your project)
Also, Take a look at the autoload section of Composer documentation.

Class not found Symfony2

I've installed the ffmpeg.so extension on my server. I want to use the ffmpeg-php library in my Symfony 2 application. If I do:
$ffmpeg = new ffmpeg_movie('movie.flv');
In a standalone php file, it works beautifully. But if I put the same code into my Symfony2 controller, I get
Fatal error: Class 'Example\ExampleBundle\Controller\ffmpeg_movie' not found in...
It must have to do with Symfony's namespace options, but I'm not sure how to resolve it.
When you're within a particular namespace, any class references without a namespace will be treated as a class local to that namespace. In your example, its treating ffmpeg_movie as a Example\ExampleBundle\Controller namespace class.
If you want to access another namespace, which includes the global namespace (which includes PHP classes, interfaces, as well as any custom defined global items), you have two choices.
Access it using it's full namespace (which is \ for global) & class name:
$obj = new \ffmpeg_movie;
$obj = new \DateTime;
Reference the external class using use:
use ffmpeg_movie;
use DateTime as AwesomeDateTimeClass;
$obj = new ffmpeg_movie;
$obj = new AwesomeDateTimeClass;
Oops, turns out you can just add
use ffmpeg_movie;
At the top of your controller, same as any other class. Silly me.

Resources