Creating a Plugin System with Phalcon - php

I have been looking into how to create a plugin system with Phalcon. I have checked INVO tutorial, Events Management to be more specific. It doesn't look like this is what I exactly need.
In this example it is loading the plugins manually and before establishing database connection.
I need to be able to access the database to check if the plugin actually installed and activated. So I can retrieve settings of installed & activated plugins as well as dynamically add them to the app.
I need to be able to attach the plugins pretty much anywhere; controllers (before / after / within method execution), models (before, after, within method execution) etc.
Does Phalcon provide such feature or do I have to ahead and try to create my own logic without using any framework feature?

Do you have any examples of what you would have plugins do? Or specifics about where Events Management is lacking?
I think you should be able able to get a very flexible system using dependency injection and events management.
It appears you can use the phalcon models before running the application, as long as you place the code after setting the db in the injector.
$plugins = \Plugin::find([
'active = :active:',
'bind'=>[
'active'=>1
]
]);
foreach($plugins as $plugin){
if(file_exists($plugin->filename)){
include $plugin->filename;
}
}
and in the file you could have code to subscribe to events, and/or add new dependencies.
// plugin file - for a db logger
$eventsManager = new \Phalcon\Events\Manager();
$logger = new \PhalconX\Logger\Adapter\Basic();
$profiler = $phalconDi->getProfiler();
$eventsManager->attach('db', function($event, $phalconConnection) use ($logger, $profiler) {
if ($event->getType() == 'beforeQuery') {
$profiler->startProfile($phalconConnection->getSQLStatement());
$logger->log($phalconConnection->getSQLStatement(), \Phalcon\Logger::INFO, $phalconConnection->getSQLVariables());
}
if ($event->getType() == 'afterQuery') {
$profiler->stopProfile();
}
});
or
class myHelper{ ... }
$phalconDi->set('myHelper', function() use ($phalconConfig, $phalconDi) {
$helper = new myHelper();
$helper->setDi( $phalconDi );
return $helper;
});

Related

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.

Running code before app starts, the best way in Silex

I am building the application, for server monitoring and monitoring data filtering. This app uses the legacy code of Zabbix monitoring tool frontend which has some config files that needs to be included and some classes to work with database.
There are 3 main things that I should do before start interacting with the database:
DBstart();
$serviceFactory = new CApiServiceFactory();
API::setApiServiceFactory($serviceFactory);
CWebUser::login($_SERVER['PHP_AUTH_USER'], '');
How can I or should I integrate these steps while using Silex?
API class with static method calls are used all over the place.
You can execute any valid php code before Silex application starts:
<?php
require_once __DIR__.'/../vendor/autoload.php';
$app = new Silex\Application();
// the code you like to execute before the app starts goes here
DBstart();
$serviceFactory = new CApiServiceFactory();
API::setApiServiceFactory($serviceFactory);
CWebUser::login($_SERVER['PHP_AUTH_USER'], '');
// Silex app starts:
$app->run();
But you should really think in terms of services and refactor your legacy code to make use of pimple container to actually integrate it. E.g.:
$app['serviceFactory'] = $app->share(function () {
return new CApiServiceFactory;
});
....
// later, when you need the API:
API::setApiServiceFactory($app['serviceFactory']);

Safely Using Magento's Pre-configuration Events

In the Magento Ecommerce System, there are three events that fire before the system is fully bootstrapped
resource_get_tablename
core_collection_abstract_load_before
core_collection_abstract_load_after
These events also fire after Magento has bootstrapped.
What's a safe and elegant (and maybe event Mage core team blessed) way to detect when Magento has fully bootstrapped so you may safely use these events?
If you attempt to use certain features in the pre-bootstrapped state, the entire request will 404. The best I've come up with (self-link for context) so far is something like this
class Packagename_Modulename_Model_Observer
{
public function observerMethod($observer)
{
$is_safe = true;
try
{
$store = Mage::app()->getSafeStore();
}
catch(Exception $e)
{
$is_safe = false;
}
if(!$is_safe)
{
return;
}
//if we're still here, we could initialize store object
//and should be well into router initialization
}
}
but that's a little unwieldy.
I don't think there is any event tailored for that.
You could add you own and file a pull request / Magento ticket to include a good one.
Until then I think the only way is to use one of the events you found and do some checks on how far Magento is initialized.
Did you try to get Mage::app()->getStores()? This might save you from the Exception catching.

Symfony2 - something other than bundle

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

Categories