I'm trying to access an FTP server from my PHP script using Codeigniter's FTP Library. These functions work great, but when testing the script I discovered that if I attempt to connect to a server that does not exist, the script does not terminate with an error message of any kind.
The page continues to execute, until the web server gives up, returning an empty document.
So I am wondering, is there a way to limit the amount of time that Codeigniter can try to connect to an FTP server, then display a message if that times out?
I tried using the php function set_time_limit(), but it does not behave how I expected it to.
Thanks for your help.
Codeigniter's ftp class uses the underlying ftp_connect php call that supports a 3rd optional parameter, timeout (http://ca2.php.net/manual/en/function.ftp-connect.php).
Codeigniter however does not use it, but allows for extending the default libraries it provides (providing that you're willing to do some work and check that any updates you do to the core will not break the functionality of your extended class). So to solve your problem you could create a new library in you application library folder:
<?php
class MY_FTP extends CI_FTP { //Assuming that in your config.php file, your subclass prefix is set to 'MY_' like so: $config['subclass_prefix'] = 'MY_';
var $timeout = 90;
/**
* FTP Connect
*
* #access public
* #param array the connection values
* #return bool
*/
function connect($config = array())
{
if (count($config) > 0)
{
$this->initialize($config);
}
if (FALSE === ($this->conn_id = ftp_connect($this->hostname, $this->port, $this->timeout)))
{
if ($this->debug == TRUE)
{
$this->_error('ftp_unable_to_connect');
}
return FALSE;
}
if ( ! $this->_login())
{
if ($this->debug == TRUE)
{
$this->_error('ftp_unable_to_login');
}
return FALSE;
}
// Set passive mode if needed
if ($this->passive == TRUE)
{
ftp_pasv($this->conn_id, TRUE);
}
return TRUE;
}
}
?>
and from your script, you could add to your configuration array the timeout option:
$this->load->library('ftp'); //if ftp is not autoloaded
$ftp_params = array('hostname'=>'1.2.3.4', 'port'=>21, 'timeout'=>10); //timout is 10 seconds instead of default 90
$ftp_conn = $this->ftp->connect($ftp_params);
if(FALSE === $ftp_conn) {
//Code to handle error
}
The ftp class is not designed to give error messages unless the debug parameter is set to TRUE in te config array, in which case it'll just display an error. However it can also be override, because all errors call the function _error() in the class. So you could set 'debug' => true in your $ftp_params array, and add a function in MY_ftp like so:
/**
* This function overrides
*/
function _error($line)
{
$this->error = $line;
}
And then have a function getError()
/**
* This function overrides
*/
function get_error()
{
return $this->error;
}
So if
$ftp_conn = $this->ftp->connect($ftp_params);
returns false, you can call
$error = $this->ftp->get_error();
to get your error and display it.
Now, you can always customize and have a more complex error handling mechanism by further customizing the class...
Hope it answers your question.
The answer is simple, don't attempt to connect to a server that doesn't exist.
Related
I need help to understand how i can make inotify work with PHP.
I have a main file where i call a instance of a inotify class i created.
This works for 30 seconds and then php throws a timeout error. In that time window it can in fact print information from new files and deleted ones. It kinda works but...
My questions for you guys are:
how can i make it persistent and stable. I mean i can set timeout requests for unlimited time but that doesn't seem to be a good practice. How to deal with this?
It's supposed to work like this? I call the function and the php
hangs in that loop until a new change happens?
My index.php
$teste = new Inotify_service();
$teste->add_watch('files');
class Inotify_service
{
private $instance;
private $watch_id;
public function __construct()
{
$this->instance = inotify_init();
stream_set_blocking($this->instance, 0); # this is needed so inotify_read while operate in non blocking mode
}
/**
* [add_watch Adds a new watch or modify an existing watch for the file or directory specified in pathname]
* #param [string] $pathname [description]
*/
public function add_watch($pathname)
{
$this->watch_id = inotify_add_watch($this->instance, $pathname, IN_CREATE | IN_DELETE);
while(true){
// read events
$events = inotify_read($this->instance);
// if the event is happening within our 'Files directory'
if ($events[0]['wd'] === $this->watch_id){
// a file was created
if($events[0]['mask'] === IN_CREATE){
printf("Created file: %s in Files directory\n", $events[0]['name']);
// a file was deleted
} else if ($events[0]['mask'] === IN_DELETE){
printf("Deleted file: %s in Files directory\n", $events[0]['name']);
}
}
}
// stop watching our directories
inotify_rm_watch($this->instance, $this->watch_id);
// close our inotify instance
fclose($this->instance);
}
I'm scraping data from an API. The problem is that I'm querying the data too often for each pageload, so I'd like to store the data on the server after the first query. This should be fine according to the TOS.
On example.com/page:
<?php include 'example.com/data'; ?>
On example.com/data:
<?php include 'api.com/include'; ?>
So I am including a page from my server, and that page is including the api data from the external server.
Question 1: How can I tell example.com/data to WRITE or save the information from api.com/include as a file on the server such as example.com/data1.php ?
Question 2: How can I tell example.com/page to php include example.com/data1.php, and if it's a 404 (doesn't exist) to include example.com/data instead?
With both questions 1 and 2 answered I can query the api once, store the data as a file, and if that file exists use the data from that page rather than have to query the api each time the page is loaded.
If you know of a better way of doing this I'd be grateful to learn it. Though it is important that I include from example.com/data from example.com/page rather than directly from api.com/include because example.com/data has the correct code handlers to properly interpret and filter the information from api/include.
If you can answer either of the two questions it would be a great starting ground for me solving the other problem.
Thank you!
You should use a class to decouple this behaviour. Something like this:
<?php
class ReadApiFromDomainDotCom
{
const TTL = 3600; // File is fresh for 3600 sec
const FILE_NAME = 'whatever.txt';
const API_ADDRESS = "http://api.whatever.com";
/**
* #return string
*/
public function get()
{
if ($this->isCacheValid()) {
return file_get_contents(self::FILE_NAME);
}
return $this->readApi();
}
/**
* #return bool
*/
private function isCacheValid()
{
if (!file_exists(self::FILE_NAME)) {
return false;
}
if ($this->isExpired()) {
return false;
}
return true;
}
/**
* #return bool
*/
private function isExpired()
{
return time() - filemtime(self::FILE_NAME) > self::TTL;
}
/**
* #return string
*/
private function readApi()
{
$data = file_get_contents(self::API_ADDRESS);
file_put_contents(self::FILE_NAME, $data);
return $data;
}
}
Then it will be easy as:
$apiReader = new ReadApiFromDomainDotCom();
$data $apiReader->get();
Haven't tested this code so you will have to fiddle with it a bit. Add namespace, paths and so on.
Can't figure this one out:
In the DB_driver.php (core system/database)
this is beginning of init function...
/**
* Initialize Database Settings
*
* #return bool
*/
public function initialize()
{
/* If an established connection is available, then there's
* no need to connect and select the database.
*
* Depending on the database driver, conn_id can be either
* boolean TRUE, a resource or an object.
*/
if ($this->conn_id)
{
return TRUE;
}
// ----------------------------------------------------------------
// Connect to the database and set the connection ID
$this->conn_id = $this->db_connect($this->pconnect);
The initialize() is called wihtin CI_Controller so when creating new objects for example like this:
$x = new PartyPooper();
$y = new PartyPooper();
initialize() in the DB_driver is called twice. Nothing strange with that, but I would expect $this->conn_id to be set when creating PartyPooper() object second time ($y)?
When is "an established connection" supposed to be true? (In the example two database connections are made when there would should only be one?)
I'm using the latest database drivers in the development branch: https://github.com/EllisLab/CodeIgniter/tree/develop/system/database
I am using the mysqli-driver with persistant connections of.
UPDATE:
I'm not a huge fan messing with core-files, but I couldn't figure out another solution here. Please tell me if there's a better way of achieving of what I want to do.
I came up with this code (using sessions to handle storing and checking for existing db-connections (of the "subdriver"-object. (mysqli object in my case)):
public function initialize()
{
/* If an established connection is available, then there's
* no need to connect and select the database.
*
* Depending on the database driver, conn_id can be either
* boolean TRUE, a resource or an object.
*/
if ($this->conn_id) {
return TRUE;
}
$conn_session_id_name = 'dbsession_conn';
if (isset($_SESSION[$conn_session_id_name])) {
$sess = $_SESSION[$conn_session_id_name];
// Set connection id object or resourse and return true
// because no more connecting has to be done
if (is_object($sess) || is_resource($sess)) {
$this->conn_id = $sess;
return TRUE;
}
}
// Connect to the database and set the connection ID
$this->conn_id = $this->db_connect($this->pconnect);
// Store conn object or resource etc into session
if (is_object($this->conn_id) || is_resource($this->conn_id)) {
$_SESSION[$conn_session_id_name] = $this->conn_id;
}
// No connection resource? Check if there is a failover else throw an error
if ( ! $this->conn_id)
{
//rest of code as before...
My application got extremly much faster because it used one connection instead of around 60.
But my original question remains:
When is this supposed to be true in the initialize() function?
if ($this->conn_id) {
return TRUE;
}
(I didn't remove it when changing the code because I suppose it has some purpose-but even if I can't figure out which)
UPDAtE2 - clarification:
In a model I have a db select statement that is supposed to return PartyPooper objects:
eg. $res = $q->result('PartyPooper');
This PartyPooper is a controller that stores information about people, like names, years , birthdays etc, but it also handles stuff like calculation of info within the same object.
class PartyPooper extends CI_Controller { .... }
But as I understand from the comments below I should do like this instead?
class PartyPooper extends CI_Controller { .... }
class PartyPooperObject { .... } //Store information about people in this object
eg. $res = $q->result('PartyPooperObject');
This piece of code
if ($this->conn_id) {
return TRUE;
}
just assures that the initialize method is only called once per object.
If it has been called on the current object already, it must not be executed another time.
I'm trying to make an error logging class, I have some functions to set up various output methods like DB, file, return and screen. I want all errors to be stored into an array and when __destruct() is called, I want to stop the client from waiting for data and the log details about errors the user experienced. This way they don't have to report errors to me.
I have 2 modes, a simple GUI to test the functionality and the actual script generates responses in JSON, machine to machine. For GUI the final dump is fine but for JSON it destroys the response. So all error reporting is off and I have to handle any errors that would be dumped on screen myself, hence the $return in function flush_log($return) which make the function return a string if set to true.
After reporting flushing the errors I want to:unset($this->log_arrays)
or empty: $this->log_arrays=Array();, but it is out of scope - I understand why, my function uses a local copy - but how do I reset the array?
[EDIT]:
I tried:
$this->log_arrays = Array();
$this->log_arrays = null;
array popping:
for ($i = 1; count($this->log_arrays); $i++)
{
array_pop($this->log_arrays);
}
But I think none of it can work because within a class function you work with copies of variables, so they're basically out of scope.
[/EDIT]:
This is an already simplified class.. :
<?php
class log_strings extends mysqli
{
private $log_arrays = Array();
public function __construct($output_to_file=false, $output_to_db=true, $fall_back_file=true, $arguments, $ip=null)
{
// Setup mysqli connection, file handle or report error if one or all have failed.
// Also check wich outputs should be used and keep that info for later.
}
public function log($level, $string)
{
$log_arrays[] = Array('level' => $level, 'string' => $string);
}
public function __destruct()
{
$this->flush_log();
}
public function flush_log($return=false)
{
if (!isset($log_arrays) && count($log_arrays) == 0)
{
return true;
}
if ($return)
{
return $this->return_output();
}
else
{
$success = false;
// if enabled, output to db
if ($this->output_to_db)
{
$success = $success || $this->mysqli_output();
}
// if enabled or if db failed and fallback is enabled, output to file
if ($this->output_to_file || ($this->fall_back_file && !$success))
{
$success = $success || $this->file_output();
}
// if neither file or db succeeded, dump on screen
if ($success = false)
{
$this->screen_dump();
}
return true;
}
unset($this->log_arrays); // <= This is what it is all about!
}
private function screen_dump()
{
foreach($this->log_arrays as $array)
{
echo "<strong>{$array['level']}</strong>{$array['string']}<br/>\n";
}
}
private function mysqli_output()
{
// Output to db functionally equal to $this->screen_dump()
}
private function file_output()
{
// Output to file functionally equal to $this->screen_dump()
}
private function return_output()
{
// Return output functionally equal to $this->screen_dump()
}
}
?>
Resetting the array should work
$this->log_arrays = array();
Unset a class property is a very bad idea. Because it may be used in other methods or other classes using a potentionally getter of your class.
Is there a simple way to log to queries to a file? I have Firebug profiling working no problem with:
resources.db.params.profiler.enabled = "true"
resources.db.params.profiler.class = "Zend_Db_Profiler_Firebug"
It would be nice to log this to a file with out writing a bunch of code.
Is there a class I can swap out with Zend_Db_Profiler_Firebug?
UPDATE: See my answer below.
As of ZF 1.11.11 there is no built in profiler class that will log queries to a file. Currently, FireBug is the only specialized Db Profiler.
Here are two ways in which you can solve it without loads of extra code though.
First, check out this answer as it shows how to extend Zend_Db_Profiler to have it log the queries to a file on queryEnd. If it doesn't quite do what you want, you can extend Zend_Db_Profiler and use the provided code as a starting point.
This next example is a slight modification of a plugin I have in some of my applications that I use to profile queries when the application is in development. This method utilizes a dispatchLoopShutdown() plugin to get an instance of the Db Profiler and log the queries to a file.
<?php /* library/My/Page/DbLogger.php */
class My_Page_DbLogger extends Zend_Controller_Plugin_Abstract
{
public function dispatchLoopShutdown()
{
$db = Zend_Controller_Front::getInstance()->getParam('bootstrap')->getResource('db');
$profiler = $db->getProfiler();
if ($profiler === NULL || !($profiler instanceof Zend_Db_Profiler))
return;
// either create your logger here based on config in application.ini
// or create it elsewhere and store it in the registry
$logger = Zend_Registry::get('dblog');
$totalQueries = $profiler->getTotalNumQueries();
$queryTime = $profiler->getTotalElapsedSecs();
$longestTime = 0;
$queries = $profiler->getQueryProfiles();
if ($queries !== false) {
$content = "\nExecuted $totalQueries database queries in $queryTime seconds<br />\n";
foreach ($queries as $query) {
// TODO: You could use custom logic here to log only selected queries
$content .= "Query (" . $query->getElapsedSecs() . "s): " . $query->getQuery() . "\n";
if ($query->getElapsedSecs() > $longestTime) {
$longestTime = $query->getElapsedSecs();
}
}
$content .= "Longest query time: $longestTime.\n" . str_repeat('-', 80);
$logger->info($content);
}
}
}
To activate this plugin, you can use code like this in your bootstrap:
/**
* Register the profiler if we are running in a non-production mode
*/
protected function _initPageProfiler()
{
if (APPLICATION_ENV == 'development') {
$front = Zend_Controller_Front::getInstance();
$front->registerPlugin(new My_Page_DbLogger());
}
}
Ideally, in the long term, you would probably want to make a class that extends Zend_Db_Profiler and allow additional options to be specified in your config such as the log file path, the log priority. This way you can leverage the existing filters to Zend_Db_Profiler.