autoload bundles that are in a specific folder - symfony

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.

Related

What is the symfony way to create an entry in db while migrating?

I have a DB created via migrations that is empty while I would like it to be filled.
Let's say I have a table called 'books' with 'title' and 'author'. What I would like to make sure that at least "knitting with dog hair" by "Kendall Crolius" is an entry in the DB.
I know how to do this in SQL and of course, I could simply put a script in my build process, that executes a command to check if such an entry exists and if not create it.
But what is the Symfony way to create an entry in DB while migrating/setup?
I would not recommend to use fixtures as they are used for test and dev environment, the library should be in the require-dev section in composer.json.
You should create your entities with migration
Use a migration with SQL queries (INSERT).
Don't add entities by entity manager, because your entity can be changed (for example: added required field and after that the migration will be executed with an error)
What you are looking for are data fixtures. I think the two most popular options are:
doctrine/doctrine-fixtures-bundle
hautelook/alice-bundle
With the former you create fixtures in code like this (taken from docs):
class AppFixtures extends Fixture
{
public function load(ObjectManager $manager)
{
// create 20 products! Bam!
for ($i = 0; $i < 20; $i++) {
$product = new Product();
$product->setName('product '.$i);
$product->setPrice(mt_rand(10, 100));
$manager->persist($product);
}
$manager->flush();
}
}
You can run them using bin/console doctrine:fixtures:load.
Whereas the latter uses YAML:
App\Entity\Dummy:
dummy_{1..10}:
name: <name()>
related_dummy: '#related_dummy*'
The command for adding the fixtures is: bin/console hautelook:fixtures:load
The AliceBundle has the additional benefit that it comes with integration for Faker, which lets you generate randomized data as well as static data like you suggested.
edit: The main reason for using fixture files is that you want to be independent from the underlying database. When you use an SQL script you might have to keep multiple versions for each supported DB and sometimes even need to adjust it for different versions (e.g. MySQL 5.7 vs. MySQL 8). An additional benefit is, that changes are usually easier to track in a version history in these files than in SQL, but that is debatable. Since Doctrine Fixtures are written in code you can use inspection tools to check if they are in sync with your entities, making it easier to spot when you need to change them.
That being said, if none of those benefits are an argument and you want to use an SQL script then you should keep schema changes and inserting data separate to prevent any issues. Arguably you should also not use migrations for inserting data. You either run the risk of failing the migration because an ID is already used or not rely on IDs and make it difficult to manage relations between data. You could avoid problems by doing INSERT IGNORE but then you will have a hard time identifying if your data is in the state you expect and run the risk of inconsistencies.
I think what you are looking for are fixtures : https://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html
You just have to write a file with your fixtures (specifying Kendall Crolius etc.) and run this command at the end of the migration process :
php bin/console doctrine:fixtures:load
I think the right way to do this is to create a Symfony console command to migrate your data after you have migrated the schema.
I too missed the Laravel seeders when I moved over to Symfony. I came to realize however that there is nothing that a Laravel seeder can do which cannot be done in a Symfony command.
Migrating the schema and filling the table with data are always two separate operations. In Laravel the seeders are run separately from the migration. I've tried migrating data and schema together and it seems that path always led to tears.
Why is that? I think it is because schema is simple relative to data. Take your gnarliest production database schema and you can quickly identify the changes you need to accomplish whatever you are trying to accomplish. But now you've migrated the schema and it is time to migrate 10+ years of production data into the new order. Perhaps your data slides right in, but mine seems to always be a rat's nest of horrors that requires a lot of special effort. My experience is that I might do and undo the schema migration a few times to get it right. The data migration is something I find that I will do perhaps 10 times because one special case or another has been overlooked.
So it makes sense to build a command specifically to migrate the data for your new schema. This is quite easy in Symfony. See https://symfony.com/doc/current/console.html#creating-a-command to get started.
Briefly the approach is like so. First create a command template
$ symfony console make:command MigrateMyDataCommand
Update your template...
class MigrateMyDataCommand extends Command
{
protected static $defaultName = 'app:mydata:migrate';
protected static $defaultDescription = 'Migrate production data to new schema';
private EntityManagerInterface $em;
//...
// construct to autowire the ORM classes
private EntityManagerInterface $em;
private int $maxnew;
private int $offset;
public function __construct(EntityManagerInterface $em) {
parent::__construct();
$this->em = $em;
}
// add arguments to the command
protected function configure(): void
{
$this->addArgument('offset', InputArgument::OPTIONAL, 'offset to start creating new data');
$this->addArgument('maxnew', InputArgument::OPTIONAL, 'maximum new records created.');
}
//...
// execute the command
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$offset = intval($input->getArgument('offset'));
$maxnew = intval($input->getArgument('maxnew'));
$io->text('Starting Migrate My Data Command');
try {
$this->em->beginTransaction();
//...
$this->em->commit();
} catch(\Exception $ex) {
$this->em->rollback();
$io->error('Migration failed');
return Command::FAILURE;
}
$io->success('My data migration successful.');
return Command::SUCCESS;
}
Run your command...
$ symfony console app:mydata:migrate 0 1
To my mind this is the proper approach. The data fixtures are perfectly fine for testing where you have a bunch of data you can sling around and not care what happens to it. Using SQL INSERT is also fine as long as what you are doing is simple. But if 10,000 users are counting on you to protect their priceless data then I think investing in a specialized command to migrate is the right thing to do.

Removing orphaned entities following 'clear-orphans' command

I'm using the OneUp Uploader Bundle, with the orphanage, and I'm unsure about how to handle cleaning up entities (that were created by the listener), after the file has been cleaned up by the 'clear-orphans' command - how is this commonly handled?
I'd love to see an event fired for each file that is cleaned up (passing the filename and the mapping) but haven't found anything (assuming the event dispatcher is available to a command).
The idea of the orphanage in the OneupUploaderBundle is that you don't pollute your upload folder with files that don't belong to any entities. This implies that if you use the uploaded files in your entities you should move them out of the orphanage. If you configured a mapping to use the orphanage, all the uploaded files will be stored in a separate folder after uploading.
Citing the documentation:
They will be moved to the correct place as soon as you trigger the uploadFiles method on the Storage.
This means that you can move the logic out of the event listener (which will be called non the less), but move it to the controller where you want to finally upload and store the files in the orphanage.
// src/Acme/Controller/AcmeController.php
class AcmeController extends Controller
{
public function storeAction()
{
// ...
if ($form->isValid()) {
$orphanageManager = $this->get('oneup_uploader.orphanage_manager')->get('gallery');
// upload all files to the configured storage
$files = $manager->uploadFiles();
// your own logic to apply the files to the entity
// ...
}
}
}
Be sure to only use the orphanage if you really have to. It has some drawbacks.

SonataAdminBundle new block type

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');

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.

Resources