I wrote custom classes and want to use them in pimcore application.
I took them to /website/lib/Custom directory on server. Afterwards, I wrote recursive script includer for each Class located in the directory and included that script in /index.php file.
It is absolutely not pimcore standard but it works.
In pimcore/config/startup.php exists snippet:
$autoloaderClassMapFiles = [
PIMCORE_CONFIGURATION_DIRECTORY . "/autoload-classmap.php",
PIMCORE_CUSTOM_CONFIGURATION_DIRECTORY . "/autoload-classmap.php",
PIMCORE_PATH . "/config/autoload-classmap.php",
];
$test = PIMCORE_ASSET_DIRECTORY;
foreach ($autoloaderClassMapFiles as $autoloaderClassMapFile) {
if (file_exists($autoloaderClassMapFile)) {
$classMapAutoLoader = new \Pimcore\Loader\ClassMapAutoloader([$autoloaderClassMapFile]);
$classMapAutoLoader->register();
break;
}
}
I guess that this provides inclusion of all those classes put into returning array from autoload-classmap.php.
Having in mind that /pimcore/config/autoload-classmap.php exists, the mentioned loop would break at first iteration so classes that I would put into custom autoload-classmap are not going to be included in project.
My question is can I change files from /pimcore directory and expect that everything would be fine after system update?
No, you should not overwrite anything in the pimcore directory, since the files in there get overwritten by the update mechanism.
You can do what you want by using the /website/config/startup.php which will not get overwritten:
https://www.pimcore.org/wiki/display/PIMCORE4/Hook+into+the+startup-process
But instead of loading all your classes as you did, take advantage of the autoloader by adding this to the /website/config/startup.php:
// The first line is not absolutely necessary, since the $autoloader variable already gets
// set in the /pimcore/config/startup.php, but it is a more future-proof option
$autoloader = \Zend_Loader_Autoloader::getInstance();
$autoloader->registerNamespace('Custom');
If you are properly using namespaces and naming your files correctly that's all you need to do.
Related
I have multiple similar websites that I want to test. Here's the current approach:
Clone an existing codeception project
Adjust the variables
Add some additional specific test cases if required
This works just fine, but has one issue. If the general test case changes that means that I have to go into every project and make the changes. So what I would rather see is a way to include a generic top level test case in every project and call it with a parameter.
I tried so with a simple include() statement, but unfortunately this doesn work. And ideas how I can accomplish this?
Here some code of my initial try:
/home/tests/site1/tests/acceptance/isOnlineCept.php
include("../1generic_tests/isonline.php");
$I = new AcceptanceTester($scenario);
$I->am('user');
$I->wantTo('see the content of the site');
$I->lookForwardTo('see the homepage');
$I->amOnPage('/');
$I->see("Tech, Geek and Rock'n'Roll");
The file I include looks like this
$I = new AcceptanceTester($scenario);
$I->am('user');
$I->wantTo('see the content of the site');
$I->lookForwardTo('see the homepage');
$I->amOnPage('/');
$I->see('something else');
Unfortunately it fails with this error
[PHPUnit_Framework_Exception] Undefined index: class
Maybe include is not the right way to do it, but what would be a better way?
You can use Page Object for this.
Create one Constants.php file using Page Object where you can define all the selectors.
Create one other file where you can keep all common operation going to be performed. E.g. Settings.php file where you can have all methods as per your requirement. you can define it with parameter too.
To access any selector and/or method from declared under page dir can be accessible by using the following line.
use Page\Constants as ConstantsPage;
To access any selector from any specific file
FileNamePage::$selectorName;
To access any method, first create the object and then call the method.
E.g.
$settings = new SettingsPage( $I )
$settings->methodName();
To call the method and/pr selector in the same file.
self::selectorName;
self::methodName();
Hope this will be helpful.
I am just wondering, I actually decided to go down a different route.
I create this file A
C:\somefolder\templates\mytemplate\joomlaoverwrites\libraries\joomla\document\html\renderer\head.php
which overwrites a joomla library file:
C:\somefolder\libraries\joomla\document\html\renderer\head.php
I use the overwrite by using
require_once(__DIR__ . '/joomlaoverwrites/libraries/joomla/document/html/renderer/head.php');
in my index.php of my template
This actually works.
What I now want to do is use my file just as a wrapper class, and sneak my changes into it before returning the answer. E.g.:
public function fetchHead($document)
{
$joomlasOriginalHead = callOriginalFunctionFetchHeadFromTheJoomlaImplementation();
//do my changes to joomlas original answer and return that
}
E.g. from that overwrite, I want to call the original file. Is that somehow possible?
I want to loop all the sub dirs in the main dirs, where I keeps all my classes, for instance,
core/
model/
page/
class_1.php
class_2.php
menu/
class_3.php
and so on...
So this is my autoload function that I place it in the init.php,
function autoload_multiple_directory($class_name){
// List all the class directories in the array.
$array_directories = array(
'core/controller/',
'core/model/',
'core/helper/'
);
// 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);
//print_r($parts);
// Set the class file name.
$file_name = strtolower(end($parts)).'.php';
// $file_name = 'class_'.strtolower($class_name).'.php';
// Loop the array.
foreach($array_directories as $path_directory){
$recursive_directory = new RecursiveDirectoryIterator($path_directory);
foreach (new RecursiveIteratorIterator($recursive_directory) as $filename => $file) {
if(file_exists(WEBSITE_DOCROOT.$file->getPath().'/'.$file_name)){
include WEBSITE_DOCROOT.$file->getPath().'/'.$file_name;
}
}
/* no problem with this, but I cannot loop the sub dirs...
if(file_exists(WEBSITE_DOCROOT.$path_directory.$file_name)){
include WEBSITE_DOCROOT.$path_directory.$file_name;
}
*
*/
}
}
spl_autoload_register('autoload_multiple_directory');
But then I get this error message,
Fatal error: Cannot redeclare class Common in C:\wamp\www\xxx\core\helper\Common.php on line 6
There is only one Common class in my project. Why does it say more than once or redeclare?
But if you look at the if(file_exists(WEBSITE_DOCROOT.$path_directory.$file_name)) that I comment out - it has no problem to load the classes. The problem of this initial loop is that it does not loop the sub dirs in a main dir, for instance, core/model/
Any ideas why and what should I do to loop the sub dirs of a main dir?
EDIT:
The problem comes from RecursiveDirectoryIterator - it loops the directories and lists all files. But what I want is only the sub directories.
Is there any chance that there are more than one copy of Common.php file exists in those folders?
Because your code does not break after including a class file the autoloader will continue seeking other files with the same name in the folder tree, and would lead to Fatal error: Cannot redeclare class XXX error. Adding break could fix the problem.
// Loop the array.
$isClassFound = false;
foreach($array_directories as $path_directory){
$recursive_directory = new RecursiveDirectoryIterator($path_directory);
foreach (new RecursiveIteratorIterator($recursive_directory) as $filename => $file) {
if(file_exists(WEBSITE_DOCROOT.$file->getPath().'/'.$file_name)){
include WEBSITE_DOCROOT.$file->getPath().'/'.$file_name;
$isClassFound = true;
}
if ($isClassFound) break;
}
if ($isClassFound) break;
}
However, the nature of your autoloader seems like it should not allow any duplicated class file names. Maybe you can write a name duplication checker to guarantee uniqueness.
EDIT:
I removed the class_exists() part from my answer because using it doesn't make much sense. Anyway, since you saw that version of my answer, and you asked me where to put class_exists() via the comment, I'll revive the code sample. you can add the following code at the beginning of the autoloader.
if (class_exists($class_name,false)) // give false to avoid automatic loading
return;
Open the start menu. In the text box write cmd, wait for the cmd program to pop up and then hit Enter.
Once the terminal window opens navigate to your root folder and then do a recursive search (through all files and folders) for the class' name:
cd C:\wamp\www\xxx
findstr /SNIP /C:"class common" *.php
There should be more than one declaration of the class.
I have a PHP daemon script running on the command line that can be connected to via telnet etc and be fed commands.
What it does with the command is based on what modules are loaded, which is currently done at the start. (psuedocode below for brevity)
$modules = LoadModules();
StartConnection();
while(true){
ListenForCommands();
}
function LoadModules(){
$modules = Array();
$dir = scandir("modules");
foreach($dir as $folder){
include("modules/".$folder."/".$folder.".php");
$modules[$folder] = new $folder;
}
}
function ListenForCommands(){
if(($command = GetData())!==false){
if(isset($modules[$command])){
$modules[$command]->run();
}
}
}
So, an example module called "bustimes" would be a class called bustimes, living in /modules/bustimes/bustimes.php
This works fine. However, I'd like to make it so modules can be updated on the fly, so as part of ListenForCommands it looks at the filemtime of the module, works out if it's changed, and if so, effectively reloads the class.
This is where the problem comes in, obviously if I include the class file again, it'll error as the class already exists.
All of the ideas I have of how to get around this problem so far are pretty sick and I'd like to avoid doing.
I have a few potential solutions so far, but I'm happy with none of them.
when a module updates, make it in a new namespace and point the reference there
I don't like this option, nor am I sure it can be done (as if I'm right, namespaces have to be defined at the top of the file? That's definitely workaroundable with a file_get_contents(), but I'd prefer to avoid it)
Parsing the PHP file then using runkit-method-redefine to redefine all of the methods.
Anything that involves that kind of parsing is a bad plan.
Instead of including the file, make a copy of the file with everything the same but str_replacing the class name to something with a rand() on the end or similar to make it unique.
Does anyone have any better ideas about how to either a) get around this problem or b) restructure the module system so this problem doesn't occur?
Any advice/ideas/constructive criticism would be extremely welcome!
You should probably load the files on demand in a forked process.
You receive a request
=> fork the main process, include the module and run it.
This will also allow you to run several commands at once, instead of having to wait for each one to run before launching the next.
Fork in php :
http://php.net/manual/en/function.pcntl-fork.php
Tricks with namespaces will fail if module uses external classes (with relative paths in namespace).
Trick with parsing is very dangerous - what if module should keep state? What if not only methods changed, but, for example, name of implemented interface? How it will affect other objects if they have link to instance of reloaded class?
I think #Kethryweryn is something you can try.
I need to be able to "effectivly" redeclare my class, so that during runtime, whilst my PHP IRC bot is running I can reload modules as the code base changes, which requires getting the class, but PHP won't allow it to be redeclared, nor is there a way to undeclare a class.
Is there a way I can acheive this? Perhaps some other method? I've tried "runkit", but that failed.
My current code is here:
http://pastie.org/private/ptj7c0t0teh3nnzn7ehcg
So to clarify, I effecivly need to be able to reload my class, instatiate it and put into a property in my main bot class once code has changed in said module, whilst the bot is running (run-time).
A brief look at your application leads me to believe your best course of action is to use a base class as suggested in the comment above.
Something to the extent of:
class IRCModule {
private static $module_dir = ''; //put your module directory here w/o trailing slash
public static function getModule( $module ) {
//if the module directory doesn't exist, don't do anything
if( !is_dir( self::$module_dir.DIRECTORY_SEPARATOR.$module ) ) return false;
//load the module file
$fname = scandir(self::$module_dir.DIRECTORY_SEPARATOR.$module);
$fname = $fname[2]; //first 2 elements will be . and ..
require_once( self::$module_dir.DIRECTORY_SEPARATOR.$module.DIRECTORY_SEPARATOR.$fname );
$className = str_replace('.class.php',NULL,$fname);
return new $className();
}
}
You would then extend that using your modules. That would allow you to overwrite a module by simply removing it's old file /my/module/dir/moduleName/moduleNameV1.0.class.php and replacing it with a new version /my/module/dir/moduleName/moduleNameV1.1.class.php
As mentioned in the comments, this will eventually fill the memory on the server, so you should schedule a reboot of the service each time you make substantial changes, but it also allows you to load new versions on demand without stopping the service.
A more stable approach would be to take advantage of process control and spin off daemons for each connection from your parent script, or implement a cache system that stores all data on the disk/database so that you can detect a change in module version and instantly reboot the server. But the solution above should work for you for the time being :)