Read controllers custom annotations from a custom Command in Symfony 2 - php

I have created custom annotations to generate JSON files (for NodeRed if you ask...) and i'm testing them successfully from a dummy method in a dummy controller.
I would like to port all that to a custom Sf command whose job would be to read all Controllers annotations in my bundle and achieve the same result (aka create JSON files).
How could i achieve that ? Would looping through XxxxController.php file(s) with the finder be a good option ? Or am too ambitious ? :p
Annotation example:
/**
* #NodeRedFlows(
* triggerBy="testFlow", options={"interval":"45"}
* )
*/
public function indexAction() { /*...*/ }
Sorry its not easy to post some more code because i have the whole reader class, the annotation class and another class creating JSON flows based on the triggerBy="testFlow" id.
Bottom line:*
I would like to be able to create my JSON flow file from a Command instead of here in my Controller (used it for tests).

Load all controller actions, which have been assigned a route in Symfony (see this and this).
Then load the annotations for every found controller action:
use Doctrine\Common\Annotations\AnnotationReader;
use Symfony\Component\HttpFoundation\Request;
$annotationReader = new AnnotationReader();
$routes = $this->container->get('router')->getRouteCollection()->all();
$this->container->set('request', new Request(), 'request');
foreach ($routes as $route => $param) {
$defaults = $params->getDefaults();
if (isset($defaults['_controller'])) {
list($controllerService, $controllerMethod) = explode(':', $defaults['_controller']);
$controllerObject = $this->container->get($controllerService);
$reflectedMethod = new \ReflectionMethod($controllerObject, $controllerMethod);
// the annotations
$annotations = $annotationReader->getMethodAnnotations($reflectedMethod );
}
}
UPDATE:
If you need all controller methods, including those without the #Route annotation, then I would do what you suggest in your question:
// Load all registered bundles
$bundles = $this->container->getParameter('kernel.bundles');
foreach ($bundles as $name => $class) {
// Check these are really your bundles, not the vendor bundles
$bundlePrefix = 'MyBundle';
if (substr($name, 0, strlen($bundlePrefix)) != $bundlePrefix) continue;
$namespaceParts = explode('\\', $class);
// remove class name
array_pop($namespaceParts);
$bundleNamespace = implode('\\', $namespaceParts);
$rootPath = $this->container->get('kernel')->getRootDir().'/../src/';
$controllerDir = $rootPath.$bundleNamespace.'/Controller';
$files = scandir($controllerDir);
foreach ($files as $file) {
list($filename, $ext) = explode('.', $file);
if ($ext != 'php') continue;
$class = $bundleNamespace.'\\Controller\\'.$filename;
$reflectedClass = new \ReflectionClass($class);
foreach ($reflectedClass->getMethods() as $reflectedMethod) {
// the annotations
$annotations = $annotationReader->getMethodAnnotations($reflectedMethod);
}
}
}

Related

How to add own translator loader/filetype/"logic" to Symfony 3

In my symfony project I want to add mulitple mail templates in my resources folder.
Filepath pattern:
app/Resources/translations/emails/[LOCALE]/[mail-type].html.twig
I've got my loader, named MailLoader witch load():
public function load($resource, $locale, $domain = 'messages')
{
$messages = [];
$label = strstr(basename($resource), '.', true);
$messages[$label] = file_get_contents($resource);
$catalogue = new MessageCatalogue($locale);
$catalogue->add($messages, $domain);
return $catalogue;
}
I want to use $translator->trans('mail-type'); to get specifc for locale content of file.
It's work when I do it like this:
$translator = new Translator('pl');
$translator->addLoader('mails', new MailLoader());
$finder = new Finder();
$finder->files()->in(__DIR__ . '/../Resources/views/email/pl');//->name('*.html.twig');
foreach ($finder as $file) {
$translator->addResource('mails', $file->getPath() . '/' . $file->getFilename(), 'pl');
}
echo $translator->trans('order-closed');//Returns file content.
But I want to extend Symfony Translator in container to use it everywhere I need. How to do it?
Please, I work wit this soo long. It's my first work with this Symfony Component and... It's made me crazy! >:D

Cannot redeclare class: how to autoload a class if exists already in a folder?

How can I check if a class exists already in a folder then do not load this class again from another folder?
I have this folder structure for instance,
index.php
code/
local/
And I have these two identical classes in code/ and local/
from local/
class Article
{
public function getArticle()
{
echo 'class from local';
}
}
from core,
class Article
{
public function getArticle()
{
echo 'class from core';
}
}
So I need a script that can detects the class of Article in local/ - if it exits already in that folder than don't load the class again from core/ folder. Is it possible?
This is my autoload function in index.php for loading classes,
define ('WEBSITE_DOCROOT', str_replace('\\', '/', dirname(__FILE__)).'/');
function autoloadMultipleDirectory($class_name)
{
// List all the class directories in the array.
$main_directories = array(
'core/',
'local/'
);
// Set other vars and arrays.
$sub_directories = array();
// When you use namespace in a class, you get something like this when you auto load that class \foo\tidy.
// So use explode to split the string and then get the last item in the exloded array.
$parts = explode('\\', $class_name);
// Set the class file name.
$file_name = end($parts).'.php';
// List any sub dirs in the main dirs above and store them in an array.
foreach($main_directories as $path_directory)
{
$iterator = new RecursiveIteratorIterator
(
new RecursiveDirectoryIterator(WEBSITE_DOCROOT.$path_directory), // Must use absolute path to get the files when ajax is used.
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($iterator as $fileObject)
{
if ($fileObject->isDir())
{
// Replace any backslash to '/'.
$pathnameReplace = str_replace('\\', '/', $fileObject->getPathname());
//print_r($pathnameReplace);
// Explode the folder path.
$array = explode("/",$pathnameReplace);
// Get the actual folder.
$folder = end($array);
//print_r($folder);
// Stop proccessing if the folder is a dot or double dots.
if($folder === '.' || $folder === '..') {continue;}
//var_dump($fileObject->getPathname());
// Must trim off the WEBSITE_DOCROOT.
$sub_directories[] = preg_replace('~.*?(?=core|local)~i', '', str_replace('\\', '/', $fileObject->getPathname())) .'/';
}
}
}
// Mearge the main dirs with any sub dirs in them.
$merged_directories = array_merge($main_directories,$sub_directories);
// Loop the merge array and include the classes in them.
foreach($merged_directories as $path_directory)
{
if(file_exists(WEBSITE_DOCROOT.$path_directory.$file_name))
{
// There is no need to use include/require_once. Autoload is a fallback when the system can't find the class you are instantiating.
// If you've already included it once via an autoload then the system knows about it and won't run your autoload method again anyway.
// So, just use the regular include/require - they are faster.
include WEBSITE_DOCROOT.$path_directory.$file_name;
}
}
}
// Register all the classes.
spl_autoload_register('autoloadMultipleDirectory');
$article = new Article();
echo $article->getArticle();
of course I get this error,
Fatal error: Cannot redeclare class Article in C:\wamp\...\local\Article.php on line 3
class_exists seems to be the answer I should look into, but how can I use it with the function above, especially with spl_autoload_register. Or if you have any better ideas?
Okay, I misunderstood your question. This should do the trick.
<?php
function __autoload($class_name) {
static $core = WEBSITE_DOCROOT . DIRECTORY_SEPARATOR . "core";
static $local = WEBSITE_DOCROOT . DIRECTORY_SEPARATOR . "local";
$file_name = strtr($class_name, "\\", DIRECTORY_SEPARATOR):
$file_local = "{$local}{$file_name}.php";
require is_file($file_local) ? $file_local : "{$core}{$file_name}.php";
}
This is easily solved by using namespaces.
Your core file goes to /Core/Article.php:
namespace Core;
class Article {}
Your local file goes to /Local/Article.php:
namespace Local;
class Article {}
And then use a very simple autoloader, e.g.:
function __autoload($class_name) {
$file_name = strtr($class_name, "\\", DIRECTORY_SEPARATOR);
require "/var/www{$file_name}.php";
}
PHP loads your classes on demand, there's no need to load the files up front!
If you want to use an article simply do:
<?php
$coreArticle = new \Core\Article();
$localArticle = new \Local\Article();

Instantiating all classes in directory

I'm using Laravel and creating artisan commands but I need to register each one in start/artisan.php by calling
Artisan::add(new MyCommand);
How can I take all files in a directory (app/commands/*), and instantiate every one of them in an array ? I'd like to get something like (pseudocode) :
$my_commands = [new Command1, new Command2, new Command3];
foreach($my_commands as $command){
Artisan::add($command);
}
Here is a way to auto-register artisan commands. (This code was adapted from the Symfony Bundle auto-loader.)
function registerArtisanCommands($namespace = '', $path = 'app/commands')
{
$finder = new \Symfony\Component\Finder\Finder();
$finder->files()->name('*Command.php')->in(base_path().'/'.$path);
foreach ($finder as $file) {
$ns = $namespace;
if ($relativePath = $file->getRelativePath()) {
$ns .= '\\'.strtr($relativePath, '/', '\\');
}
$class = $ns.'\\'.$file->getBasename('.php');
$r = new \ReflectionClass($class);
if ($r->isSubclassOf('Illuminate\\Console\\Command') && !$r->isAbstract() && !$r->getConstructor()->getNumberOfRequiredParameters()) {
\Artisan::add($r->newInstance());
}
}
}
registerArtisanCommands();
If you put that in your start/artisan.php file, all commands found in app/commands will be automatically registered (assuming you follow Laravel's recommendations for command and file names). If you namespace your commands like I do, you can call the function like so:
registerArtisanCommands('App\\Commands');
(This does add a global function, and a better way to do this would probably be creating a package. But this works.)
<?php
$contents = scandir('dir_path');
$files = array();
foreach($contents as $content) {
if(substr($content,0,1 == '.') {
continue;
}
$files[] = 'dir_path'.$content;
}
That reads the contents of a folder, itterates over it and saves the filename including path in the $files array. Hope this is what you're looking for
PS: Im not familiar with laravel or artisan. So if you have to use specific semantics(like camelCase) to register them, then please tell me so

PHPUnit Dry Run or Discover Test without executing

I would like to write a script that "discovers" PHPUnit test in a given folder.
Currently I can execute:
phpunit .
And all my test names are shown however they are executed which can take quite a bit of time.
What I would like is the ability to view which test I have in a project without actually executing the test. In a format similar to
ExampleTest::testNameHere
Is this possible?
Thank you for your time.
You're looking for:
--list-tests
/* phpunit/php-file-iterator */
$iteratorFactory = new File_Iterator_Factory();
$paths = array(
/* testsuite path */
'/var/www/project/tests/unit/phpUnit/',
);
$suffixes = array(
'Test.php',
);
$iterator = $iteratorFactory->getFileIterator($paths, $suffixes);
foreach ($iterator as $file) {
$fileName = $file->getFileName();
$path = $file->getPathName();
// you should use autoloader
require_once $path;
// build className, according to your file structure
$class = preg_replace('/\.php/', '', $fileName);
$refrlection = new ReflectionClass($class);
$methods = $refrlection->getMethods(ReflectionMethod::IS_PUBLIC);
foreach ($methods as $method) {
// test* is test method
if (preg_match('/^test[A-Z]/', $method->getName())) {
echo $refrlection->getName() . ':' . $method->getName();
echo PHP_EOL;
}
}
}
Code that detects if a file contains:
A class that inherits the PHPUnit base class for test classes (sorry, don't have the name in front of me)
Methods in that class that start with 'test'
should do the trick.

Add caching to Zend\Form\Annotation\AnnotationBuilder

As I've finally found a binary of memcache for PHP 5.4.4 on Windows, I'm speeding up the application I'm currently developing.
I've succeeded setting memcache as Doctrine ORM Mapping Cache driver, but I need to fix another leakage: Forms built using annotations.
I'm creating forms according to the Annotations section of the docs. Unfortunately, this takes a lot of time, especially when creating multiple forms for a single page.
Is it possible to add caching to this process? I've browsed through the code but it seems like the Zend\Form\Annotation\AnnotationBuilder always creates the form by reflecting the code and parsing the annotations. Thanks in advance.
You might wanna try something like this:
class ZendFormCachedController extends Zend_Controller_Action
{
protected $_formId = 'form';
public function indexAction()
{
$frontend = array(
'lifetime' => 7200,
'automatic_serialization' => true);
$backend = array('cache_dir' => '/tmp/');
$cache = Zend_Cache::factory('Core', 'File', $frontend, $backend);
if ($this->getRequest()->isPost()) {
$form = $this->getForm(new Zend_Form);
} else if (! $form = $cache->load($this->_formId)) {
$form = $this->getForm(new Zend_Form);
$cache->save($form->__toString(), $this->_formId);
}
$this->getHelper('layout')->setLayout('zend-form');
$this->view->form = $form;
}
Found here.
Louis's answer didn't work for me so what I did was simply extend AnnotationBuilder's constructor to take a cache object and then modified getFormSpecification to use that cache to cache the result. My function is below..
Very quick work around...sure it could be improved. In my case, I was limited to some old hardware and this took the load time on a page from 10+ seconds to about 1 second
/**
* Creates and returns a form specification for use with a factory
*
* Parses the object provided, and processes annotations for the class and
* all properties. Information from annotations is then used to create
* specifications for a form, its elements, and its input filter.
*
* MODIFIED: Now uses local cache to store parsed annotations
*
* #param string|object $entity Either an instance or a valid class name for an entity
* #throws Exception\InvalidArgumentException if $entity is not an object or class name
* #return ArrayObject
*/
public function getFormSpecification($entity)
{
if (!is_object($entity)) {
if ((is_string($entity) && (!class_exists($entity))) // non-existent class
|| (!is_string($entity)) // not an object or string
) {
throw new Exception\InvalidArgumentException(sprintf(
'%s expects an object or valid class name; received "%s"',
__METHOD__,
var_export($entity, 1)
));
}
}
$formSpec = NULL;
if ($this->cache) {
//generate cache key from entity name
$cacheKey = (is_string($entity) ? $entity : get_class($entity)) . '_form_cache';
//get the cached form annotations, try cache first
$formSpec = $this->cache->getItem($cacheKey);
}
if (empty($formSpec)) {
$this->entity = $entity;
$annotationManager = $this->getAnnotationManager();
$formSpec = new ArrayObject();
$filterSpec = new ArrayObject();
$reflection = new ClassReflection($entity);
$annotations = $reflection->getAnnotations($annotationManager);
if ($annotations instanceof AnnotationCollection) {
$this->configureForm($annotations, $reflection, $formSpec, $filterSpec);
}
foreach ($reflection->getProperties() as $property) {
$annotations = $property->getAnnotations($annotationManager);
if ($annotations instanceof AnnotationCollection) {
$this->configureElement($annotations, $property, $formSpec, $filterSpec);
}
}
if (!isset($formSpec['input_filter'])) {
$formSpec['input_filter'] = $filterSpec;
}
//save annotations to cache
if ($this->cache) {
$this->cache->addItem($cacheKey, $formSpec);
}
}
return $formSpec;
}

Categories