Object Oriented PHP issues using a class, involving PrestaShop module - php

What I want to accomplish
I am altering a PrestaShop module that currently uses a form submitted by a user to generate files to be a stand alone CRON job. The module works perfectly in the back office, and involves nothing more than a user clicking a button; a repetitive task that should instead be handled by a CRON job (the action, not the clicking of the button of course).
What I am trying
if (!defined('_PS_VERSION_')) {
// Initialize prestashop
require_once '../../config/config.inc.php';
require_once '../../init.php';
define('_PS_MODE_DEV_', true);
echo 'This gets echoed';
$exporter = new order_exporter;
echo 'This does not get echoed';
}
class order_exporter extends Module
{
// Rest of code here. Works when used with back office.
}
The behavior I am getting
The first echo works, but once I call $exporter = new order_exporter;, I get this error. PHP Fatal error: Class 'order_exporter' not found in C:\wamp\www\addressstamps\modules\order_exporter\order_exporter.php. This isn't on the screen and only in my error log. As I've understood in the past, this is the correct way to use OOP. I'm not sure if I'm misunderstanding something about Prestashop, OOP, or Scope, but after much research and tweaking, I have made no progress past this point.

This is not an OOP issue, but a PrestaShop design decision.
Try with:
$exporter = Module::getInstanceByName('order_exporter');
instead of:
$exporter = new order_exporter;
Here 'order_exporter' is your module name (i.e the name property from the class).

Related

Can't extend php class

I'm trying to move a php/mysql web application to a new server. The application runs fine on multiple other servers, just not the one the client wants it on. Problem 1 is I can't get errors to display on screen or in an error log. (I've posted a separate question about this). I'm hoping if I can get error to display I'll have more to go on, but what I know so far is it fails when trying to extend a module. I stripped down the class to just this:
class Module_Organization extends LmsModule {
function Module_Organization($module_name = '') {
die('Made it into the function');
}
}
Nothing happens. But if I change it to:
class Module_Organization {
function Module_Organization($module_name = '') {
die('Made it into the function');
}
}
then it does execute the die statement. So it seems that the extend portion is tripping it up. But I don't think it's the code because I know this exact code works fine on other 5 other servers. So I'm wondering if there is any server configuration that could prevent php from extending a class.
Thank you in advance for your help.
I compared the php.ini file to a server where it is working to make them match and it is now working. I'm not sure which changed fixed it though. Thank you.

Reloading a 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.

Getting CakeS3 to work in the CakeShell

I want to be able to call the CakeS3 plugin from the Cake Shell. However, as I understand it components cannot be loaded from the shell. I have read this post outlining strategies for overcoming it: using components in Cakephp 2+ Shell - however, I have had no success. The CakeS3 code here is similar to perfectly functioning cake S3 code in the rest of my app.
<?php
App::uses('Folder','Utility');
App::uses('File','Utility');
App::uses('CakeS3.CakeS3','Controller/Component');
class S3Shell extends AppShell {
public $uses = array('Upload', 'User', 'Comment');
public function main() {
$this->CakeS3 = new CakeS3.CakeS3(
array(
's3Key' => 'key',
's3Secret' => 'key',
'bucket' => 'bucket')
);
$this->out('Hello world.');
$this->CakeS3->permission('private');
$response = $this->CakeS3->putObject(WWW_ROOT . '/file.type' , 'file.type', $this->CakeS3->permission('private'));
if ($response == false){
echo "it failed";
} else {
echo "it worked";
}
}
This returns an error of "Fatal error: Class 'CakeS3' not found in /home/app/Console/Command/S3Shell.php. The main reason I am trying to get this to work is so I can automate some uploads with a cron. Of course, if there is a better way, I am all ears.
Forgive me this "advertising"... ;) but my plugin is probably better written and has a better architecture than this CakeS3 plugin if it is using a component which should be a model or behaviour task. Also it was made for exactly the use case you have. Plus it supports a few more storage systems than only S3.
You could do that for example in your shell:
StorageManager::adapter('S3')->write($key, StorageManager::adapter('Local')->read($key));
A file should be handled as an entity on its own that is associated to whatever it needs to be associated to. Every uploaded file (if you use or extend the models that come with the plugin, if not you have to take care of that) is stored as a single database entry that contains the name of the config that was used and some meta data for that file. If you do the line of code above in your shell you will have to keep record in the table if you want to access it this way later. Just check the examples in the readme.md out. You don't have to use the database table as a reference to your files but I really recommend the system the plugin implements.
Also, you might not be aware that WWW_ROOT is public accessible, so in the case you store sensitive data there it can be accessed publicly.
And finally in a shell you should not use echo but $this->out() for proper shell output.
I think the App:uses should look like:
App::uses('CakeS3', 'CakeS3.Controller/Component');
I'm the author of CakeS3, and no I'm afraid there is no "supported" way to do this as when we built this plugin, we didn't need to run uploads from shell and just needed a simple interface to S3 from our controllers. We then open sourced the plugin as a simple S3 connector.
If you'd like to have a go at modifying it to support shell access, I'd welcome a PR.
I don't have a particular road map for the plugin, so I've tagged your issue on github as an enhancement and will certainly consider it in future development, but I can't guarantee that it would fit your time requirements so that's why I mention you doing a PR.

redeclare variable, or effectivly reload class at runtime in php

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 :)

PHP - SOAP-ENV:ClientBad Request after SoapServer->Handle() - Help

I got a PHP web service that had been running great for a long time, but somewhere a long the way it some how stopped working, and I just can't get it to work again.
I got some php page in which all I do is define a class with functions, and in the end of it I create the SoapServer.
It looks like this -
class MyClassWS
{
function .....
function ....
}
ini_set("soap.wsdl_cache_enabled", "0");
error_log("Server after ini_set");
$soapserver = new SoapServer("MyWSDL.wsdl");
error_log("Server after new SoapServer");
$soapserver->setClass("MyClassWS");
error_log("Server after setClass");
//error_log(print_r($soapserver->getFunctions()));
try
{
$soapserver->handle();
}
catch(Exception $ex)
{
error_log("Exception!".$ex->getMessage());
}
error_log("Finished Handling",0);
Right after the $soapserver->handle(); the code terminates, and I get a vague "SOAP-ENV:ClientBad Request" result on my web page.
This happens when I "require_once" this page, from my index page, so I could call functions that are defined in this class, from my index page.
My guess is that maybe I've been fiddling too much with my WSDL and somehow it screwed up my while WebService, but I tried looking on what's wrong with it, but couldn't get onto anything. Especially because of this annoying vague error message, that doesn't really help.
Thanks!
I think I nicely fixed it by separating the WebService class that included the SoapServer into 2 different classes.
One that included only the class with the functions, and another that included the WebServer and only had a reference to the class.
This way I can import the class with the functions without also importing the SoapServer and triggering the handle() function.

Categories