I am building a custom plugin, which consists of schema which I am trying to keep tightly coupled with the plugin. For example, I have a plugins/userPlugin/config/doctrine/schema.yml which references a 'store-rw-user' connection. My goal is to force the project's databases.yml to contain a connection for 'store-rw-user', this way the project will be responsible for setting up the separate user connection.
However, when I attempt to access plugin related code I keep getting: Doctrine_Manager_Exception Unknown connection: store-rw-user.
Here is the related snippet of the plugin schema file:
# plugins/userPlugin/config/doctrine/schema.yml
connection: store-rw-user
User:
tableName: user
actAs: [Timestampable]
And here is the related snippet of the BaseUser.class.php (generated when doing a model build):
<?php
// lib/model/doctrine/userPlugin/base/BaseUser.class.php
// Connection Component Binding
Doctrine_Manager::getInstance()->bindComponent('User', 'store-rw-user');
....
And finally the related snippet of the project's databases.yml file:
# config/databases.yml
all:
store-rw-user:
class: sfDoctrineDatabase
param:
....
On the outset, everything looks configured correctly. The plugin schema.yml resulted in the base class binding the correct database connection, which exists in the project's databases.yml file. However, I am running into the aforementioned issue. I'd like to know if there is another approach to this issue before I start trying to manipulate doctrine connection manager manually via the plugins initialize() method.
-- Update --
After further investigation, it appears that sfDatabaseManager is aware of the 'store-rw-user' connection, however, Doctrine_Manager is not. At first I thought this was maybe due to the order in which I added plugins within the main project config file, but it only affected whether sfDatabaseManager was connection aware, but for the sake of completeness, here is the snippet of the project config file:
// config/ProjectConfiguration.class.php
class ProjectConfiguration extends sfProjectConfiguration
{
....
public function setup()
{
....
$this->enablePlugins(
array(
'sfDoctrinePlugin',
'userPlugin'
)
);
}
}
And just in case it matters, here is the app.yml for the userPlugin:
# plugins/userPlugin/config/app.yml
# This entry was needed so build-model was executed, the plugin schema file would be pulled in for build as well
all:
userPlugin:
config_dir: %SF_PLUGINS_DIR%/userPlugin/config
recursive: true
And here is the confusing snippet of code, where I have access to 'store-rw-user' connection via sfDatabaseManager but not Doctrine_Manager, which is a problem because the error is being thrown from Doctrine_Manager and not sfDatabaseManager:
// plugins/userPlugin/config/userPluginConfiguration.class.php
class userPluginConfiguration extends sfPluginConfiguration
{
public function initialize()
{
parent::initialize();
var_dump(Doctrine_Manager::getInstance()->getConnections());
$config = ProjectConfiguration::getActive();
$manager = new sfDatabaseManager($config);
var_dump($manager->getDatabase('store-rw-user'));
exit;
}
}
And the results:
array (size=0)
empty
object(sfDoctrineDatabase)[53]
protected '_doctrineConnection' =>
object(Doctrine_Connection_Mysql)[55]
protected 'driverName' => string 'Mysql' (length=5)
protected 'dbh' => null
protected 'tables' =>
array (size=0)
empty
protected '_name' => string 'store-rw-user' (length=13)
....
Not sure what's going on, tried looking at other plugins to see how they deal, and they don't have any database references that I could find.
After adding some debug code to select source files (Doctrine_Manager), I finally saw that in the above code snippet in the initialize() method, that by simply configuring sfDatabaseManager, doctrine then became aware of the connection. In short, my method ended up looking like this:
class userPluginConfiguration extends sfPluginConfiguration
{
public function initialize()
{
parent::initialize();
// Don't ask me why, without this the needed db connection never gets initialized and plugin craps out
$manager = new sfDatabaseManager(ProjectConfiguration::getActive());
}
}
And, magically, doctrine was now aware of all the connections in the project's databases.yml file. Seems weak to me that I have to explicitly make this call, but at least I can get on with some coding.
Related
I setup a Unit Test in a Shopware custom (static) Plugin following this guide:
Shopware documentation
Everything runs fine and I'm able to run a unit test
class ProductReturnsTest extends TestCase
{
use IntegrationTestBehaviour;
use StorefrontPageTestBehaviour;
public function testConfirmPageSubscriber(): void
{
$container = $this->getKernel()->getContainer();
$dd = $container->get(CustomDataService::class); <== IT BREAKS HERE ServiceNotFoundException: You have requested a non-existent service
$dd = $container->get('event_dispatcher'); // WORKS WITH SHOPWARE ALIASES NOT WITH PLUGINS
}
}
I can make container->get on any shopware alias but as soon I try to recall and get from the container any service decleared in any xml of any 3th party plugin, i get
ServiceNotFoundException: You have requested a non-existent service "blabla"
What is wrong ?
Take a look at the answer given here: https://stackoverflow.com/a/70171394/10064036.
Probably your plugin is not marked as active in the DB your tests run against.
The test environment has a mostly unpopulated database to allow tests to to run unaffected with their own fixtures only. Therefore after each test there should be a rollback to all transactions made within the test. This principle also includes plugin installations and database transactions they may execute in their lifecycle events.
You may want to install your plugin properly before your tests, so you get a representative state of the environment with the plugins lifecycle events getting dispatched and thereby caused possible changes.
public function setUp(): void
{
$this->installPlugin();
}
private function installPlugin(): void
{
$application = new Application($this->getKernel());
$installCommand = $application->find('plugin:install');
$args = [
'--activate' => true,
'--reinstall' => false,
'plugins' => ['YourPluginName'],
];
$installCommand->run(new ArrayInput($args, $installCommand->getDefinition()), new NullOutput());
}
In a Symfony 2.7 app, we have attempted to set up a humanize_bytes Twig filter in order to convert long numbers of bytes into human-readable form -- 10 MB, for example.
Within our HumanReadableBytesExtension.php file is the following:
public function getFilters() {
return [
new TwigFilter('humanize_bytes', [$this, 'getHumanReadableBytesFilter'])
];
}
... and in our services.yml file lies the following:
mycompany.cms.twig.extension.human_readable_bytes_extension:
class: MyCompany\TwigExtensions\HumanReadableBytesExtension
arguments:
- '#translator'
tags:
- {name: twig.extension}
... but we find that the getFilters() method is not getting called, and that when we try to call the filter in a Twig template, we get:
Unknown "humanize_bytes" filter.
Both files pass syntax validation. The cache has been cleared. Is there somewhere else where we should be registering this filter?
====
Edit: Here is the output of the app/console debug:container mycompany.cms.twig.extension.human_readable_bytes_extension command:
[container] Information for service
mycompany.cms.twig.extension.human_readable_bytes_extension Service Id
mycompany.cms.twig.extension.human_readable_bytes_extension Class
MyCompany\TwigExtensions\HumanReadableBytesExtension Tags
- twig.extension () Scope container Public yes Synthetic no Lazy no
Synchronized no Abstract no
You mentioned you are using an abstract class. Did you override the getName method in your HumanReadableBytesExtension ?
If two extensions have the same name, only one will be loaded, the second will be silently ignored.
I ultimately just took all of my changes and put them on a fresh feature branch. That "fixed" the problem, albeit in a very non-satisfactory way. (We never really did figure out what was going wrong.)
I'm (we're) creating a package that acts as a core component for our future CMS and of course that package needs some unit tests.
When the package registeres, the first thing it does is set the back/frontend context like this:
class FoundationServiceProvider extends ServiceProvider
{
// ... stuff ...
public function register()
{
// Switch the context.
// Url's containing '/admin' will get the backend context
// all other urls will get the frontend context.
$this->app['build.context'] = request()->segment(1) === 'admin'
? Context::BACKEND
: Context::FRONTEND;
}
}
So when I visit the /admin url, the app('build.context') variable will be set to backend otherwise it will be set to `frontend.
To test this I've created the following test:
class ServiceProviderTest extends \TestCase
{
public function test_that_we_get_the_backend_context()
{
$this->visit('admin');
$this->assertEquals(Context::BACKEND, app('build.context'));
}
}
When I'm running the code in the browser (navigating to /admin) the context will get picked up and calling app('build.context') will return backend, but when running this test, I always get 'frontend'.
Is there something I did not notice or some incorrect code while using phpunit?
Thanks in advance
Well, this is a tricky situation. As I understand it, laravel initiates two instances of the framework when running tests - one that is running the tests and another that is being manipulated through instructions. You can see it in tests/TestCase.php file.
So in your case you are manipulating one instance, but checking the context of another (the one that did not visit /admin and is just running the tests). I don't know if there's a way to access the manipulated instance directly - there's nothing helpful in documentation on this issue.
One workaround would be to create a route just for testing purposes, something like /admin/test_context, which would output the current context, and the check it with
$this->visit('admin/test_context')->see(Context::BACKEND);
Not too elegant, but that should work. Otherwise, look around in laravel, maybe you will find some undocumented feature.
I have a Yaml loader that loads additional config items for a "profile" (where one application can use different profiles, e.g. for different local editions of the same site).
My loader is very simple:
# YamlProfileLoader.php
use Symfony\Component\Config\Loader\FileLoader;
use Symfony\Component\Yaml\Yaml;
class YamlProfileLoader extends FileLoader
{
public function load($resource, $type = null)
{
$configValues = Yaml::parse($resource);
return $configValues;
}
public function supports($resource, $type = null)
{
return is_string($resource) && 'yml' === pathinfo(
$resource,
PATHINFO_EXTENSION
);
}
}
The loader is used more or less like this (simplified a bit, because there is caching too):
$loaderResolver = new LoaderResolver(array(new YamlProfileLoader($locator)));
$delegatingLoader = new DelegatingLoader($loaderResolver);
foreach ($yamlProfileFiles as $yamlProfileFile) {
$profileName = basename($yamlProfileFile, '.yml');
$profiles[$profileName] = $delegatingLoader->load($yamlProfileFile);
}
So is the Yaml file it's parsing:
# profiles/germany.yml
locale: de_DE
hostname: %profiles.germany.host_name%
At the moment, the resulting array contains literally '%profiles.germany.host_name%' for the 'hostname' array key.
So, how can I parse the % parameters to get the actual parameter values?
I've been trawling through the Symfony 2 code and docs (and this SO question and can't find where this is done within the framework itself. I could probably write my own parameter parser - get the parameters from the kernel, search for the %foo% strings and look-up/replace... but if there's a component ready to be used, I prefer to use this.
To give a bit more background, why I can't just include it into the main config.yml: I want to be able to load app/config/profiles/*.yml, where * is the profile name, and I am using my own Loader to accomplish this. If there's a way to wildcard import config files, then that might also work for me.
Note: currently using 2.4 but just about ready to upgrade to 2.5 if that helps.
I've been trawling through the Symfony 2 code and docs (and this SO question and can't find where this is done within the framework itself.
Symfony's dependency injection component uses a compiler pass to resolve parameter references during the optimisation phase.
The Compiler gets the registered compiler passes from its PassConfig instance. This class configures a few compiler passes by default, which includes the ResolveParameterPlaceHoldersPass.
During container compilation, the ResolveParameterPlaceHoldersPass uses the Container's ParameterBag to resolve strings containing %parameters%. The compiler pass then sets that resolved value back into the container.
So, how can I parse the % parameters to get the actual parameter values?
You'd need access to the container in your ProfileLoader (or wherever you see fit). Using the container, you can recursively iterate over your parsed yaml config and pass values to the container's parameter bag to be resolved via the resolveValue() method.
Seems to me like perhaps a cleaner approach would be for you to implement this in your bundle configuration. That way your config will be validated against a defined structure, which can catch configuration errors early. See the docs on bundle configuration for more information (that link is for v2.7, but hopefully will apply to your version also).
I realise this is an old question, but I have spent quite a while figuring this out for my own projects, so I'm posting the answer here for future reference.
I tried a lot of options to resolve %parameter% to parameters.yml but no luck at all. All I can think of is parsing %parameter% and fetch it from container, no innovation yet.
On the other hand I don't have enough information about your environment to see the big picture but I just come up with another idea. It can be quite handy if you declare your profiles in your parameters.yml file and load it as an array in your controller or service via container.
app/config/parameters.yml
parameters:
profiles:
germany:
locale: de_DE
host_name: http://de.example.com
uk:
locale: en_EN
host_name: http://uk.example.com
turkey:
locale: tr_TR
host_name: http://tr.example.com
You can have all your profiles as an array in your controller.
<?php
namespace Acme\DemoBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class DefaultController extends Controller
{
public function indexAction()
{
$profiles = $this->container->getParameter('profiles');
var_dump($profiles);
return $this->render('AcmeDemoBundle:Default:index.html.twig');
}
}
With this approach
you don't have to code a custom YamlLoader
you don't have to worry about importing parameters into other yml files
you can have your profiles as an array anytime you have the $container in your hand
you don't have to load/cache profile files one by one
you don't have to find a wildcard file loading solution
If I got your question correctly, this approach can help you.
I am using Zend Framework 2.2.2 and Doctrine2 Module 0.7.0.
My goal is to have my functions related to a task in a standalone php-class. My current workflow is between two different programms: get data -> modify and store data -> send data.
This workflow needs functions from 3 ZF2 modules:
1. source software module
2. internal storage mechanism module
3. destination software module
The first task is successfull but when I move my data to the second module like this (shrinked to the main code):
use MTNewsletterEngine\Controller\NewsletterEngineController;
/** #var \MTNewsletterEngine\Controller\NewsletterEngineController */
private $_newsletterEngine;
$this->_newsletterEngine = new NewsletterEngineController();
[...]
$this->_newsletterEngine->addNewNewsletterRecipient($emailAddresses,1);
The second Controller has problems getting the service locator:
Fatal error: Call to a member function get() on a non-object in C:\xampp\htdocs\app\trunk\module\MTNewsletterEngine\src\MTNewsletterEngine\Controller\NewsletterEngineController.php on line 51
Line 51:
$em_mtnewsletterengine = $this->getServiceLocator()->get('doctrine.entitymanager.orm_mtnewsletterengine');
NewsletterEngineController is the Main Controller from Module MTNewsletterEngine.
I am confused as I don't know how to get this solved. Thanks.
Do not create a new instance of NewsletterEngineController by using the new keyword. The ServiceLocator will not be injected to the created object this way. Use Zend\ServiceManager to retrieve an instance of Zend\Mvc\Controller\ControllerManager (alias: "ControllerLoader" (ci)) and use the get method, to load the target controller. Zend\Mvc\Controller\ControllerManager extends the ServiceManager itself (because it is a plugin manager).
Check your module.config.php. The controller should be listed as an invokable controller.
Example:
'controllers' => array(
'invokables' => array(
'MTNewsletterEngine\Controller\NewsletterEngine' => 'MTNewsletterEngine\Controller\NewsletterEngineController'
),
),
$this->_newsletterEngine = $this->getServiceLocator()
->get('ControllerLoader')
->get('MTNewsletterEngine\Controller\NewsletterEngine');
For more information read the manual and try to understand the way the ServiceManager / ServiceLocator (which is part of Zend\Di) works.
Maybe you should also think about the structure of your application. I am not sure what you are trying to do there but it seems like you are mixing up different application layers.
Docs
http://framework.zend.com/manual/2.2/en/index.html#zend-di
http://framework.zend.com/manual/2.2/en/index.html#zend-servicemanager