Instantiating all classes in directory - php

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

Related

How can you use the ResourceWatcher bundle from YoSymfony?

I am trying to make a file watcher where, when you add, update or delete a file, you can see the files updates in a database. I'm using the framework Symfony4 and a bundle from it called ResourceWatcher from YoSymfony. This bundle uses the Finder bundle from Symfony to find files in the directories specified and then, the watcher compares the cache and the new file to see if there are any changes. When I use a method with the watcher which returns a path array, when I try to see the array, it returns null. How am I suppose to use these methods and their returns?
I put the var_dump everywhere to see that the problem comes from the findChanges()->getUpdatedFiles() and getNewFiles();
//OLD CODE
$finder = new Finder();
$finder->files()
->name('*.csv')
->in('%kernel.root_dir%/../src/data/');
//watcher
$hashContent = new Crc32ContentHash();
$resourceCache = new ResourceCachePhpFile('cache-key.php');
$watcher = new ResourceWatcher($resourceCache, $finder, $hashContent);
$watcher->initialize();
if($watcher->findChanges()->hasChanges()){
if($watcher->findChanges()->getNewFiles() === null){
$paths = $watcher->findChanges()->getUpdatedFiles();
}
else{
$paths = $watcher->findChanges()->getNewFiles();
}
$propertyAccessor = PropertyAccess::createPropertyAccessor();
var_dump($propertyAccessor->getValue($paths, '[first_name]'));
die();
}
I'd like to be able to see the paths, convert them into string and use that into my other method to make the data appear in my database.
In my var_dump, I get NULL in terminal.
EDIT:[first_name] is in my csv-file, you can dump $paths directly.
//NEW CODE
$finder = new Finder();
$finder->files()
->name('*.csv')
->in('%kernel.root_dir%/../src/data/');
//watcher
$hashContent = new Crc32ContentHash();
$resourceCache = new ResourceCachePhpFile('cache-key.php');
$watcher = new ResourceWatcher($resourceCache, $finder, $hashContent);
$watcher->initialize();
$changes = $watcher->findChanges();
if(!empty($changes->getUpdatedFiles())){
$updatedFilesPath = $changes->getUpdatedFiles();
$pathString = implode($updatedFilesPath);
$reader = Reader::createFromPath($pathString);
}
elseif(!empty($changes->getNewFiles())){
$newFilesPath = $changes->getNewFiles();
$pathString = implode($newFilesPath);
$reader = Reader::createFromPath($pathString);
}
else{
return;
}
$results = $reader->fetchAssoc();
So it looks like that as soon as you use the method findChanges()->hasChanges(), it tells the watcher that there is some changes but then it resets and there's no changes anymore in the watcher so it's pointless to use
$paths = $watcher->findChanges()->getUpdatedFiles();
since it will always return nothing because of the reset. I had to make a variable with the changes inside so that I could re-use the changes further down.
Details in code...

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

Read controllers custom annotations from a custom Command in Symfony 2

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

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

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.

Categories