Symfony2 - something other than bundle - php

I'm currenly working on the project where i need something orther than bundle. Something i call "Module".
It should be different from the bundle in that when project is starting system doesn't know which "Modules" will be used
and how they will be configured.
Also i'm going to use these modules similar to bundles
$response = $this->forward('AcmeHelloModule:Hello:fancy');
OR
$response = $this->forward('Acme/Hello:Hello:fancy');
Here HelloController->fancyAction(); would be executed. And this controller described say in file /src/modules/Acme/Hello/Controller/HelloController.php
So the question is how to implement this ?

a solution would be to implement a PluginBundle that can dynamicly install, load and run your so called modules.
the PluginBundle would not contain specific plugin code at all, but the runtime environment for you modules/plugins. you may then save in the database which plugins/modules are enabled and load them dynamicly at runtime.
with this sollution it should be possible to create a dynamic plugin mechanism as in wordpress. modifying the AppKernel at runtime is not a good solution because you'd also have to clear the cache when en- disabeling bundles.

In AppKernel add the following method:
public function getBundle($name, $first = true)
{
if (substr($name, -6) == 'Module')) {
return $this->getBundle('ModuleBundle')->getModule($name, $first);
}
return parent::getBundle($name, $first);
}
and all the logic runs in ModuleBundle.
But make sure the type of response is the same as Kernel->getBundle();

Related

Yii2: How to force using fallback MessageFormatter method?

My website is with a hosting provider that has the MessageFormatter class available on the server (Linux, PHP 7.0.27) but it is an old ICU version (4.2.1) that doesn't support my message {number,plural,=0{# available} =1{# available} other{# available}} and gives the error:
Message pattern is invalid: Constructor failed
msgfmt_create: message formatter creation failed: U_ILLEGAL_CHARACTER
...because of the =1 and =2 notation.
I'm not able to make changes to the server so how can I force using the fallback method provided by Yii2 which works just fine?
There is this hacky way you can try.
Copy the yii\i18n\MessageFormatter code to a new file. Name it MessageFormatter.php and place somewhere in your application (but not in vendor folder).
In this new file change the format() method to:
public function format($pattern, $params, $language)
{
$this->_errorCode = 0;
$this->_errorMessage = '';
if ($params === []) {
return $pattern;
}
return $this->fallbackFormat($pattern, $params, $language);
}
Don't change anything else (including namespace).
Now let's use Yii mapping.
Find a place in your application when you can put code that will be run every time in bootstrapping phase. Good place for this is common/config/bootstrap.php if you are using "Advanced Template"-like project.
Add there this line:
Yii::$classMap['yii\i18n\MessageFormatter'] = 'path/to/your/MessageFormatter.php';
Obviously change the path to the one you've chosen. Now Yii autoloader will load this class from your file instead of the original Yii vendor folder (as mentioned in Class Autoloading section of the Guide).
In the modified file MessageFormatter method presence of intl library is never checked so fallback is used as default.
The downside of this trick is that you need to update manually your file every time original Yii file is changed (so almost every time you upgrade Yii version).
Another approach is to configure I18N component in your application to use your custom MessageFormatter where you can extend the original file and just override format() method inside without modifying class map.

Loading Modules Dynamically in Zend Framework 2

I have asked this question yesterday as well, but this one includes code.
Issue
My application have multiple modules and 2 types of user accounts, Some modules are loaded always which are present in application.config.php some of them are conditional i.e. some are loaded for user type A and some for user type B
After going through documentations and questions on Stack Overflow, I understand some of ModuleManager functionalities and started implementing the logic that I though might work.
Some how I figured out a way to load the modules that are not present in application.config.php [SUCCESS] but their configuration is not working [THE ISSUE] i.e. if in onBootstrap method I get the ModuleManager service and do getLoadedModules() I get the list of all the modules correctly loaded. Afterwards if I try to get some service from that dynamically loaded module, it throws exception.
Zend\ServiceManager\ServiceManager::get was unable to fetch or create an instance for jobs_mapper
Please note that, the factories and all other stuff are perfectly fine because if I load the module from application.config.php it works fine
Similarly when I try to access any route from the dynamically loaded module it throws 404 Not Found which made it clear that the configuration from module.config.php of these modules are not loading even though the module is loaded by ModuleManager.
Code
In Module.php of my Application module I implemented InitProviderInterface and added a method init(ModuleManager $moduleManager) where I catch the moduleManager loadModules.post event trigger and load modules
public function init(\Zend\ModuleManager\ModuleManagerInterface $moduleManager)
{
$eventManager = $moduleManager->getEventManager();
$eventManager->attach(\Zend\ModuleManager\ModuleEvent::EVENT_LOAD_MODULES_POST, [$this, 'onLoadModulesPost']);
}
Then in the same class I delcare the method onLoadModulesPost and start loading my dynamic modules
public function onLoadModulesPost(\Zend\ModuleManager\ModuleEvent $event)
{
/* #var $serviceManager \Zend\ServiceManager\ServiceManager */
$serviceManager = $event->getParam('ServiceManager');
$configListener = $event->getConfigListener();
$authentication = $serviceManager->get('zfcuser_auth_service');
if ($authentication->getIdentity())
{
$moduleManager = $event->getTarget();
...
...
$loadedModules = $moduleManager->getModules();
$configListener = $event->getConfigListener();
$configuration = $configListener->getMergedConfig(false);
$modules = $modulesMapper->findAll(['is_agency' => 1, 'is_active' => 1]);
foreach ($modules as $module)
{
if (!array_key_exists($module['module_name'], $loadedModules))
{
$loadedModule = $moduleManager->loadModule($module['module_name']);
//Add modules to the modules array from ModuleManager.php
$loadedModules[] = $module['module_name'];
//Get the loaded module
$module = $moduleManager->getModule($module['module_name']);
//If module is loaded succesfully, merge the configs
if (($loadedModule instanceof ConfigProviderInterface) || (is_callable([$loadedModule, 'getConfig'])))
{
$moduleConfig = $module->getConfig();
$configuration = ArrayUtils::merge($configuration, $moduleConfig);
}
}
}
$moduleManager->setModules($loadedModules);
$configListener->setMergedConfig($configuration);
$event->setConfigListener($configListener);
}
}
Questions
Is it possible to achieve what I am trying ?
If so, what is the best way ?
What am I missing in my code ?
I think there is some fundamental mistake in what you are trying to do here: you are trying to load modules based on merged configuration, and therefore creating a cyclic dependency between modules and merged configuration.
I would advise against this.
Instead, if you have logic that defines which part of an application is to be loaded, put it in config/application.config.php, which is responsible for retrieving the list of modules.
At this stage though, it is too early to depend on any service, as service definition depends on the merged configuration too.
Another thing to clarify is that you are trying to take these decisions depending on whether the authenticated user (request information, rather than environment information) matches a certain criteria, and then modifying the entire application based on that.
Don't do that: instead, move the decision into the component that is to be enabled/disabled conditionally, by putting a guard in front of it.
What you're asking can be done, but that doesn't mean you should.
Suggesting an appropriate solution without knowing the complexity of the application you're building is difficult.
Using guards will certainly help decouple your code, however using it alone doesn't address scalability and maintainability, if that's a concern?
I'd suggest using stateless token-based authentication. Instead of maintaining the validation logic in every application, write the validation logic at one common place so that every request can make use of that logic irrespective of application. Choosing a reverse proxy server (Nginx) to maintain the validation logic (with the help of Lua) gives you the flexibility to develop your application in any language.
More to the point, validating the credentials at the load balancer level essentially eliminates the need for the session state, you can have many separate servers, running on multiple platforms and domains, reusing the same token for authenticating the user.
Identifying the user, account type and loading different modules then becomes a trivial task. By simply passing the token information via an environment variable, it can be read within your config/application.config.php file, without needing to access the database, cache or other services beforehand.

Can I use the Blade templating engine outside of Laravel?

i'm want to creating a design pattern and use the "Blade templating engine".
Can I use the Blade templating engine outside of Laravel and use it in my new pattern ?
For the record:
I tested many libraries to run blade outside Laravel (that i don't use) and most are poor hacks of the original library that simply copied and pasted the code and removed some dependencies yet it retains a lot of dependencies of Laravel.
So I created (for a project) an alternative for blade that its free (MIT license, i.e. close source/private code is OK) in a single file and without a single dependency of an external library. You could download the class and start using it, or you could install via composer.
https://github.com/EFTEC/BladeOne
https://packagist.org/packages/eftec/bladeone
It's 100% compatible without the Laravel's own features (extensions).
How it works:
<?php
include "lib/BladeOne/BladeOne.php";
use eftec\bladeone;
$views = __DIR__ . '/views'; // folder where is located the templates
$compiledFolder = __DIR__ . '/compiled';
$blade=new bladeone\BladeOne($views,$compiledFolder);
echo $blade->run("Test.hello", ["name" => "hola mundo"]);
?>
Another alternative is to use twig but I tested it and I don't like it. I like the syntax of Laravel that its close to ASP.NET MVC Razor.
Edit: To this date (July 2018), it's practically the only template system that supports the new features of Blade 5.6 without Laravel. ;-)
You certainly can, there are lots of standalone blade options on packagist, as long as you are comfortable with composer then there should be no issue, this one looks pretty interesting due to having a really high percentage of stars compared to downloads.
Be warned though i have not tried it myself, like you i was looking for a standalone option for my own project and came across it, i will be giving it a real good workout though at sometime in the near future,
Matt Stauffer has created a whole repository showing you how you can use various Illuminate components directly outside of Laravel. I would recommend following his example and looking at his source code.
https://github.com/mattstauffer/Torch
Here is the index.php of using Laravel Views outside of Laravel
https://github.com/mattstauffer/Torch/blob/master/components/view/index.php
You can write a custom wrapper around it so that you can call it like Laravel
use Illuminate\Container\Container;
use Illuminate\Events\Dispatcher;
use Illuminate\Filesystem\Filesystem;
use Illuminate\View\Compilers\BladeCompiler;
use Illuminate\View\Engines\CompilerEngine;
use Illuminate\View\Engines\EngineResolver;
use Illuminate\View\Engines\PhpEngine;
use Illuminate\View\Factory;
use Illuminate\View\FileViewFinder;
function view($viewName, $templateData)
{
// Configuration
// Note that you can set several directories where your templates are located
$pathsToTemplates = [__DIR__ . '/templates'];
$pathToCompiledTemplates = __DIR__ . '/compiled';
// Dependencies
$filesystem = new Filesystem;
$eventDispatcher = new Dispatcher(new Container);
// Create View Factory capable of rendering PHP and Blade templates
$viewResolver = new EngineResolver;
$bladeCompiler = new BladeCompiler($filesystem, $pathToCompiledTemplates);
$viewResolver->register('blade', function () use ($bladeCompiler) {
return new CompilerEngine($bladeCompiler);
});
$viewResolver->register('php', function () {
return new PhpEngine;
});
$viewFinder = new FileViewFinder($filesystem, $pathsToTemplates);
$viewFactory = new Factory($viewResolver, $viewFinder, $eventDispatcher);
// Render template
return $viewFactory->make($viewName, $templateData)->render();
}
You can then call this using the following
view('view.name', ['title' => 'Title', 'text' => 'This is text']);
Yes you can use it where ever you like. Just install one of the the many packages available on composer for it.
If you're interested in integrating it with codeigniter I have a blog post here outlining the process.
Following the above steps should make it obvious how to include it into any framework.

Testing Laravel Service Providers

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.

Symfony 2 - how to parse %parameter% in my own Yaml file loader?

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.

Categories