I am trying to create an FTP connection in PHP and trying to access it from other PHP files. Basically my goal is to create an FTP connection and make it available like a session when logging in and then access it from my other PHP files to do other tasks like file upload-download.
My connection class is as follows,
<?php
if($_GET['q'] == 'ftp_connect')
{
$connection = new connect_to_ftp;
echo $connection->ftp_connection();
}
elseif($_GET['q'] == 'get_connection_id')
{
$connection = new connect_to_ftp;
echo $connection->get_connection();
}
class connect_to_ftp
{
public $ftp_server = "";
public $username = "";
public $password = "";
public $connectionID, $login_result, $response;
public function ftp_connection()
{
$this->connectionID = ftp_connect($this->ftp_server);
if($this->connectionID==true)
{
$this->response = array("connection_error" => "Connected to ".$this->ftp_server);
$this->login_result = ftp_login($this->connectionID, $this->username, $this->password);
if($this->login_result==true)
{
$this->response = array("connection_error" => "Login successful to ".$this->ftp_server);
}else
{
$this->response = array("connection_error" => "Failed to login to ".$this->ftp_server." Check username and password");
}
}else
{
$this->response = array("connection_error" => "Could not connect to ".$this->ftp_server);
}
return json_encode($this->response);
}
public function get_connection()
{
return $this->connectionID;
}
}
?>
When I call ftp_connection() using ajax, it successfully connects to the ftp account but later on when I call get_connection() to return me the connection id, it returns me null instead.
Short answer: You can't
Longer answer: file handles, including sockets only exist within the process which opens them (this is not strictly true, it is possible to pass file handles between processes on Linux, but it is very contentious functionality and as with most esoteric things should not be used unless you fully understand the ramifications, and the functionality is not exposed in php).
The options are to either
1) create a connection, carry out the necessary operations then close it within the instance of a php script. Unfortunately FTP is even more badly thought than SMTP but nearly as deeply ingrained. So lots of people have invented built-in security "features" to try to address it's shortcomings. A common one is rate limiting of connections.
2) run an event based daemon to act as the FTP client and connect to that using php. However if you don't understand files and processes then you have a lot of learning to do before you can write such a programming.
Did I mention that FTP sucks? Learn how to use SFTP.
Related
I have been following along with the tutorials here and got the ratchet server working.
My chat class is the same as the tutorial more or less at the moment, so no point in showing that here yet since my question is more about implementation strategy.
In the question I attached the user was looking how to get the connection object of a specific user. In the top answer solution keeping track of the resource IDs seems to be the way to do this.
For example when the connection is created there is this code.
public function onOpen(ConnectionInterface $conn) {
// Store the new connection to send messages to later
$this->clients[$conn->resourceId] = $conn;
echo "New connection! ({$conn->resourceId})\n";
}
This creates a member variable clients to store all the connections and you simply reference it now by ID to send a message. This clients however is an instance of ConnectionInterface $conn
Then to send a message you simply use the code below entering as the array key the id of the client. Very simple.
$client = $this->clients[{{insert client id here}}];
$client->send("Message successfully sent to user.");
As we know ratchet runs as a script on the server in an event loop never ending.
I'm running a Symfony project in which outside of the server instance running the ratchet code when a user does a certain action in the system I need it to send a message to a particular client connected to the server.
I'm not sure how to do this since the clients are instances of ConnectionInterface and are created when the users first connect via WebSockets. How do I send a message to a particular client in this way?
Here is a visual of what I'm trying to achieve.
References:
how to get the connection object of a specific user?
The solution I am about to post covers the entire process of communicating from server to the client on the web browser including a way to make the Websocket server run in the background (with and without docker).
Step 1:
Assuming you have ratchet installed via composer, create a folder in your project called bin and name the file "startwebsocketserver.php" (or whatever you want)
Step 2:
Copy the following code into it.
<?php
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use React\Socket\Server;
use React\EventLoop\Factory;
use WebSocketApp\Websocketserver;
use WebSocketApp\Htmlserver;
use WebSocketApp\Clientevent;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Ratchet\App;
require dirname(__DIR__) . '/vendor/autoload.php';
require_once dirname(__DIR__) . '/bootstrap/bootstrap.php';
$websocketserver = new Websocketserver();
$dispatcher = new EventDispatcher(); //#JA - This is used to maintain communication between the websocket and HTTP Rest API Server
$dispatcher->addListener('websocketserver.updateclient', array($websocketserver, 'updateClient'));
//// 1. Create the event loop
$loop = Factory::create();
//// 2. Create websocket servers
$webSock = new Server($loop);
new IoServer(
new HttpServer(
new WsServer( $websocketserver )
),
$webSock
);
$webSock->listen('8080', '0.0.0.0');
$app = new App( 'localhost', 6677, '0.0.0.0',$loop );
$app->route( '/', new Htmlserver(), [ '*' ] );//#JA - Allow any origins for last parameter
$app->run();
Note that in my example I am using a bootstrap file to load the database. If you are not using a database or some other method than ignore that. For the purposes of this answer I will be assuming Doctrine 2 as the database.
What this code does is creates an HTTP server & a WebSocket server within the same code base and at the same time. I'm using the $app->route approach since you can add further routing for the HTTP server to organize API Calls to talk to the WebSocket Server from your PHP Web Server.
The $loop variable includes the Websocket server in the application loop along with the HTTPServer.
Step 3:
In your project directory create a folder called websockets. Inside that create another folder called WebSocketApp. Inside that create 3 empty files for now.
Clientevent.php
Htmlserver.php
Websocketserver.php
We will go into each of these files 1 by 1 next. Failure to create these directories in this order will cause composer Autoload PSR-0 to fail to find them.
You can change the names but make sure you edit your composer file accordingly.
Step 4:
In your composer.json file make sure it looks something like this.
{
"require": {
"doctrine/orm": "^2.5",
"slim/slim": "^3.0",
"slim/twig-view": "^2.1",
"components/jquery": "*",
"components/normalize.css": "*",
"robloach/component-installer": "*",
"paragonie/random_compat": "^2.0",
"twilio/sdk": "^5.5",
"aws/aws-sdk-php": "^3.22",
"mailgun/mailgun-php": "^2.1",
"php-http/curl-client": "^1.7",
"guzzlehttp/psr7": "^1.3",
"cboden/ratchet": "^0.3.6"
},
"autoload": {
"psr-4": {
"app\\":"app",
"Entity\\":"entities"
},
"psr-0": {
"WebSocketApp":"websockets"
},
"files": ["lib/utilities.php","lib/security.php"]
}
}
In my case I'm using doctrine & slim, the important part is the "autoload" section. This section in particular is important.
"psr-0": {
"WebSocketApp":"websockets"
},
This will autoload anything in the folder websockets in the namespace of WebSocketApp. psr-0 assumed that code would be organized by folders for namespaces which is why we had to add another folder called WebSocketApp inside of websockets.
Step 5:
In the htmlserver.php file put this...
<?php
namespace WebSocketApp;
use Guzzle\Http\Message\RequestInterface;
use Guzzle\Http\Message\Response;
use Guzzle\Http\Message\Request;
use Ratchet\ConnectionInterface;
use Ratchet\Http\HttpServerInterface;
class Htmlserver implements HttpServerInterface {
protected $response;
public function onOpen( ConnectionInterface $conn, RequestInterface $request = null ) {
global $dispatcher;
$this->response = new Response( 200, [
'Content-Type' => 'text/html; charset=utf-8',
] );
$query = $request->getQuery();
parse_str($query, $get_array);//#JA - Convert query to variables in an array
$json = json_encode($get_array);//#JA - Encode to JSON
//#JA - Send JSON for what you want to do and the token representing the user & therefore connected user as well.
$event = new ClientEvent($json);
$dispatcher->dispatch("websocketserver.updateclient",$event);
$this->response->setBody('{"message":"Successfully sent message to websocket server")');
echo "HTTP Connection Triggered\n";
$this->close( $conn );
}
public function onClose( ConnectionInterface $conn ) {
echo "HTTP Connection Ended\n";
}
public function onError( ConnectionInterface $conn, \Exception $e ) {
echo "HTTP Connection Error\n";
}
public function onMessage( ConnectionInterface $from, $msg ) {
echo "HTTP Connection Message\n";
}
protected function close( ConnectionInterface $conn ) {
$conn->send( $this->response );
$conn->close();
}
}
The purpose of this file is to make communication to the WebSocket server simple through basic HTTP which I will show a demo of later using cURL from the PHP Web Server. I designed this to propagate messages to the WebSocket server using Symfony's Event system and by looking at the Query String and converting it to a JSON string. It could have also been kept as an array if you wish, but in my case I needed the JSON string.
Step 6:
Next in the clientevent.php put this code...
<?php
namespace WebSocketApp;
use Symfony\Component\EventDispatcher\Event;
use Entity\User;
use Entity\Socket;
class Clientevent extends Event
{
const NAME = 'clientevent';
protected $user; //#JA - This returns type Entity\User
public function __construct($json)
{
global $entityManager;
$decoded = json_decode($json,true);
switch($decoded["command"]){
case "updatestatus":
//Find out what the current 'active' & 'busy' states are for the userid given (assuming user id exists?)
if(isset($decoded["userid"])){
$results = $entityManager->getRepository('Entity\User')->findBy(array('id' => $decoded["userid"]));
if(count($results)>0){
unset($this->user);//#JA - Clear the old reference
$this->user = $results[0]; //#JA - Store refernece to the user object
$entityManager->refresh($this->user); //#JA - Because result cache is used by default, this will make sure the data is new and therefore the socket objects with it
}
}
break;
}
}
public function getUser()
{
return $this->user;
}
}
Note that the User and Socket entities are entities I created from Doctrine 2. You can use whatever database you prefer. In my case I am needing to send messages to particular users from the PHP Web Server based on their login tokens from the database.
Clientevent assumes JSON string of '{"command":"updatestatus","userid":"2"}'
You can set it up however you like though.
Step 7:
In the Websocketserver.php file put this...
<?php
namespace WebSocketApp;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Symfony\Component\EventDispatcher\Event;
use Entity\User;
use Entity\Authtoken;
use Entity\Socket;
class Websocketserver implements MessageComponentInterface {
protected $clients;
public function updateClient(Event $event)
{
$user = $event->getUser();//#JA - Get reference to the user the event is for.
echo "userid=".$user->getId()."\n";
echo "busy=".($user->getBusy()==false ? "0" : "1")."\n";
echo "active=".($user->getActive()==false ? "0" : "1")."\n";
$json["busy"] = ($user->getBusy()==false ? "0" : "1");
$json["active"] = ($user->getActive()==false ? "0" : "1");
$msg = json_encode($json);
foreach($user->getSockets() as $socket){
$connectionid = $socket->getConnectionid();
echo "Sending For ConnectionID:".$connectionid."\n";
if(isset($this->clients[$connectionid])){
$client = $this->clients[$connectionid];
$client->send($msg);
}else{
echo "Client is no longer connected for this Connection ID:".$connectionid."\n";
}
}
}
public function __construct() {
$this->clients = array();
}
public function onOpen(ConnectionInterface $conn) {
// Store the new connection to send messages to later
$this->clients[$conn->resourceId] = $conn;
echo "New connection! ({$conn->resourceId})\n";
}
public function onMessage(ConnectionInterface $from, $msg) {
global $entityManager;
echo sprintf('Connection %d sending message "%s"' . "\n", $from->resourceId, $msg);
//#JA - First step is to decode the message coming from the client. Use token to identify the user (from cookie or local storage)
//#JA - Format is JSON {token:58d8beeb0ada3:4ffbd272a1703a59ad82cddc2f592685135b09f2,message:register}
$json = json_decode($msg,true);
//echo 'json='.print_r($json,true)."\n";
if($json["message"] == "register"){
echo "Registering with server...\n";
$parts = explode(":",$json["token"]);
$selector = $parts[0];
$validator = $parts[1];
//#JA - Look up records in the database by selector.
$tokens = $entityManager->getRepository('Entity\Authtoken')->findBy(array('selector' => $selector, 'token' => hash('sha256',$validator)));
if(count($tokens)>0){
$user = $tokens[0]->getUser();
echo "User ID:".$user->getId()." Registered from given token\n";
$socket = new Socket();
$socket->setUser($user);
$socket->setConnectionid($from->resourceId);
$socket->setDatecreated(new \Datetime());
$entityManager->persist($socket);
$entityManager->flush();
}else{
echo "No user found from the given cookie token\n";
}
}else{
echo "Unknown Message...\n";
}
}
public function onClose(ConnectionInterface $conn) {
global $entityManager;
// The connection is closed, remove it, as we can no longer send it messages
unset($this->clients[$conn->resourceId]);
//#JA - We need to clean up the database of any loose ends as well so it doesn't get full with loose data
$socketResults = $entityManager->getRepository('Entity\Socket')->findBy(array('connectionid' => $conn->resourceId));
if(count($socketResults)>0){
$socket = $socketResults[0];
$entityManager->remove($socket);
$entityManager->flush();
echo "Socket Entity For Connection ID:".$conn->resourceId." Removed\n";
}else{
echo "Was no socket info to remove from database??\n";
}
echo "Connection {$conn->resourceId} has disconnected\n";
}
public function onError(ConnectionInterface $conn, \Exception $e) {
echo "An error has occurred: {$e->getMessage()}\n";
$conn->close();
}
}
This is the most complicated file to explain. To start there is a protected variable clients that stores every connection made to this ratchet websocket server. Its created in the onOpen event.
Next the onMessage event is where the web browser clients will register themselves for receiving messages. I did this using a JSON protocol. An example is in the code of the format I used in particular in which I used the token from their cookie to identify what user it was in my system along with a simple register message.
I simple look in the database in this function to see if there is an authToken to go along with the cookie.
If there is write to the Socket table in your database the $from->resourceId
This is the number that ratchet uses to keep track of that particular connection number.
Next in the onClose method note that we have to make sure to remove the entries we created when the connection closes so the database doesn't get filled with unnecessary and extra data.
Finally note that the updateClient function is a symfony Event that is triggered from the HtmlServer we did earlier.
This is what actually sends the message to the client web browser. First in case that user has many web browsers open creating different connections we loop through all known sockets related to that user. Doctrine makes this easy with $user->getSockets(), you will have to decide best way to do this.
Then you simply say $client->send($msg) to send the message to the web browser.
Step 8:
Finally in your javascript for your webbrowser put something like this.
var hostname = window.location.hostname; //#JA - Doing it this way will make this work on DEV and LIVE Enviroments
var conn = new WebSocket('ws://'+hostname+':8080');
conn.onopen = function(e) {
console.log("Connection established!");
//#JA - Register with the server so it associates the connection ID to the supplied token
conn.send('{"token":"'+$.cookie("ccdraftandpermit")+'","message":"register"}');
};
conn.onmessage = function(e) {
//#JA - Update in realtime the busy and active status
console.log(e.data)
var obj = jQuery.parseJSON(e.data);
if(obj.busy == "0"){
$('.status').attr("status","free");
$('.status').html("Free");
$(".unbusy").css("display","none");
}else{
$('.status').attr("status","busy");
$('.status').html("Busy");
$(".unbusy").css("display","inline");
}
if(obj.active == "0"){
$('.startbtn').attr("status","off");
$('.startbtn').html("Start Taking Calls");
}else{
$('.startbtn').attr("status","on");
$('.startbtn').html("Stop Taking Calls");
}
};
My demo here shows simple way to pass information back and forth with JSON.
Step 9:
To send messages from the PHP Web server I did something like this in a helper function.
function h_sendWebsocketNotificationToUser($userid){
//Send notification out to Websocket Server
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "http://localhost/?command=updatestatus&userid=".$userid);
curl_setopt($ch, CURLOPT_PORT, 6677);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
curl_close($ch);
}
This would attempt to send the updateStatus message for a particular user at anytime.
Step 10:
There is no step 10 you are done! Well okay not quite... To run the webserver in the background I use Docker which makes it easy. Simply execute the webserver with the following command.
docker exec -itd draftandpermit_web_1 bash -c "cd /var/www/callcenter/livesite; php bin/startwebsocketserver.php"
or something of this equivlent for your situation. Key here is the -d option I'm using which runs it in the background. Even if you run the command again it will NOT spawn two instances which is nifty. Shutting down the server is outside the scope of this but if you find a nice way to do this please amend or comment on this answer.
Also don't forget to open the ports correctly on your docker-compose file. I did something like this for my project.
ports:
- "80:80"
- "8080:8080"
- "6060:80"
- "443:443"
- "6677:6677"
#This is used below to test on local machines, just portforward this on your router.
- "8082:80"
Just remember 8080 is used by the WebSockets so it has to pass through completely.
In case you are curious about entity and database structure and what I used here is an attached image.
Hello Stack Overflow,
I am building a browser-based text only multi-player RPG written in PHP with Ratchet as the backbone.
What I have so far: It works very well. I have implemented a simple and effective command interpretor that does a good job of transferring data between the client and server. I'm able to easily perform database operations and instantiate outside classes inside my Server class to use to pass information back to the client.
Where I've gotten stuck: For some reason, my brain broke trying to implement ticks, which in the context of my game, is a set of events that happens every 45 seconds. It's basically the heartbeat of the game, and I can't move forward without having a reliable and graceful implementation of it. The tick needs to do a multitude of things, including (but not limited to): sending messages to players, updating player regen, memory handling, and so on. Generally, all these actions can be coded and placed in an Update class.
But I can't figure out how to get the tick to actually happen. The tick itself, just a function that occurs every 45 seconds inside my react loop, it should start when the server starts. It absolutely needs to be server-side. I could technically implement it client-side and sync with values in a database but I do NOT want to go down that road.
I feel like this should be easier than my brain is making it.
What I've tried:
I've tried running a simple recursive function that constructs my update class on a timer using sleep(45), but again, this needs to start when the server starts, and if I toss an infinite looping function in the construct of my server class, the startup script never gets passed that and the game never starts.
I've tried using the onPeriodicTimer function that comes with react, but I can't figure out how to implement it..
I've tried something crazy like using node js to send a message to my server every 45 seconds and my interpreter catches that particular message and starts the tick process. This is the closest I've gotten to a successful implementation but I'm really hoping to be able to do it without a client having to connect and talk to the server, it seems hackey.
I've tried ZeroMQ to achieve the same goal as above (a client that sends a message to my server that triggers the update) but again, I don't want to have to have a client listener constantly connected for the game to run, and also, zeroMQ is a lot to deal with for something so small.. I had no luck with it.
There has to be a better way to achieve this. Any help would be appreciated.
For reference, here is a basic outline of out my socket application is working. To start, I used the "Hello World" tutorial on the Ratchet website.
So I have a startup.php script that I run to initialize the Server class, which accepts messages from connected clients. onMessage, an interpretor class is instantiated which parses the message out and looks for the command the client passed in a database table which loads the corresponding Class and Method for that command, that data is based back to the onMessage function, the class and method for the command is called, and the result is passed back to the client.
TLDR: How do I add a repeating function to a Ratchet websocket server that can send messages to connected clients every 45 seconds?
Here's the Server class:
class Server implements MessageComponentInterface
{
public $clients;
public function __construct()
{
$this->clients = new \SplObjectStorage;
//exec("nodejs ../bin/java.js", $output);
}
public function onOpen(ConnectionInterface $conn)
{
$conn->connected_state = 0;
$this->clients->attach($conn);
// Initiate login
$login = new Login('CONN_GETNAME');
if($login->success)
{
$conn->send($login->output);
$conn->connected_state = $login->new_state;
$conn->chData = new Character();
}
echo "New connection! ({$conn->resourceId})\n";
}
public function onMessage(ConnectionInterface $from, $msg)
{
if($msg == 'do_tick')
{
echo "a tick happened <br>";
}
else
{
if($from->connected_state == 'CONN_CONNECTED' || $msg == 'chardump')
{
$interpretor = new Interpret($msg);
if($interpretor->success)
{
$action_class_var = $interpretor->class;
$action_method_var = $interpretor->function;
$action_class = new $action_class_var($this->clients, $from, $interpretor->msg);
$action = $action_class->{$action_method_var}();
foreach($this->clients as $client)
{
if($action->to_room)
{
if($from != $client)
{
$client->send($action->to_room);
}
}
if($action->to_global)
{
if($from != $client)
{
$client->send($action->to_global);
}
}
if($action->to_char)
{
$client->send($action->to_char);
}
}
}
else
{
$from->send('Huh?');
}
}
else
{
$login = new Login($from->connected_state, $msg, $from);
$from->connected_state = $login->new_state;
if($login->char_data && count($login->char_data)>0)
{
foreach($login->char_data as $key=>$val)
{
$from->chData->{$key} = $val;
}
}
$from->send($login->output);
}
}
}
public function onClose(ConnectionInterface $conn) {
$this->clients->detach($conn);
echo "Connection {$conn->resourceId} has disconnected\n";
}
public function onError(ConnectionInterface $conn, \Exception $e) {
echo "An error has occurred: {$e->getMessage()}\n";
$conn->close();
}
Perhaps an onTick function added to this class that gets called every X seconds? Is that possible?
To broadcast the message to everyone in intervals of 45 seconds (or any other number), you must control the event loop which Ratchet uses.
You need to add a timed event, various vendors call this timed event, timer event, repeatable event, but it always behaves the same - a function fires after X amount of time.
Class that you are after is documented at this link
Alternatively, you can use icicle instead of Ratchet. I personally prefer it, I don't have any particular reason for the preference - both libraries are excellent in my opinion, and it's always nice to have an alternative.
Interestingly enough, you tried to use ZeroMQ - it's a transport layer and it's definitely one of the best libraries / projects I've ever used. It plays nicely with event loops, it's definitely interesting for developing distributed systems, job queues and similar.
Good luck with your game! If you'll have any other questions regarding WS, scaling to multiple machines or similar - feel free to ping me in the comments below this answer.
Thank you, N.B.!
For anyone that might be stuck in a similar situation, I hope this helps someone out. I had trouble even figuring out what terms I should be googling to get to the bottom of my problem, and as evidenced by the comments below my original question, I got flack for not being "specific" enough. Sometimes it's hard to ask a question if you're not entirely sure what you're looking for!
Here is what the game's startup script looks like now, with an implemented "tick" loop that I've tested.
<?php
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use React\Socket\Server as Reactor;
use React\EventLoop\Factory as LoopFactory;;
require dirname(__DIR__) . '/vendor/autoload.php';
foreach(new DirectoryIterator(dirname(__DIR__) .'/src/') as $fileInfo)
{
if($fileInfo->isDot() || $fileInfo->isDir())
{
continue;
}
require_once(dirname(__DIR__) . '/src/' . $fileInfo->getFilename());
}
$clients = null;
class Server implements MessageComponentInterface
{
public function __construct(React\EventLoop\LoopInterface $loop)
{
global $clients;
$clients = new \SplObjectStorage;
// Breathe life into the game
$loop->addPeriodicTimer(40, function()
{
$this->doTick();
});
}
public function onOpen(ConnectionInterface $ch)
{
global $clients;
$clients->attach($ch);
$controller = new Controller($ch);
$controller->login();
}
public function onMessage(ConnectionInterface $ch, $args)
{
$controller = new Controller($ch, $args);
if($controller->isLoggedIn())
{
$controller->interpret();
}
else
{
$controller->login();
}
}
public function onClose(ConnectionInterface $conn)
{
global $clients;
$clients->detach($conn);
echo "Connection {$conn->resourceId} has disconnected\n";
}
public function onError(ConnectionInterface $conn, \Exception $e)
{
echo "An error has occurred: {$e->getMessage()}\n";
$conn->close();
}
public function doTick()
{
global $clients;
$update = new Update($clients);
}
}
$loop = LoopFactory::create();
$socket = new Reactor($loop);
$socket->listen(9000, 'xx.xx.xx.xxx');
$server = new IoServer(new HttpServer(new WsServer(new Server($loop))), $socket, $loop);
$server->run();
I have been reading several posts plus the official guides about how to connect more than one database in CI. I currently connect to the default application database using the standard database.php configuration and load the other database on the fly when needed. The main purpose of this part of the app is to have an "import" feature where the users user inputs the foreign database connection data on the fly when requested.
As long as the second database connection data is correctly set, the app works like a breeze. When there's an error in the connection config I didn't find a working method to evaluate that a connection could not be estabilished to the other database.
I found out that I could check if $db->conn_id is false to eventually return an error to the user but for some reasons it returns an object no matter what.
This is a brief example of what I'm doing inside the model:
function plugin_subscribers_import_sfguard($channel_id)
{
// Get the channel object by its ID
$channel = $this->get_channel_by('id',$channel_id);
// Set the preferences for the second database
// from the channel informations we retrieved
$db['hostname'] = $channel->host;
$db['username'] = $channel->user;
$db['password'] = $channel->password;
$db['database'] = $channel->db;
$db['dbdriver'] = "mysql";
$db['pconnect'] = FALSE;
$db['db_debug'] = TRUE;
// Open the connection to the 2nd database
$other_db = $this->load->database($db,TRUE);
if ( ! $other_db->conn_id )
{
// This never gets executed as $other_db->conn_id always
// returns: "resource(34) of type (mysql link)"
return false;
}
else
{
// Execute the rest of the import script
$other_db->select('*');
$other_db->from('sf_guard_user');
$other_db->join('sf_guard_user_profile',
'sf_guard_user_profile.id=sf_guard_user.id',
'inner');
$query = $other_db->get();
}
}
I wonder if there's something I didn't get out of the whole thing or if I'm using the wrong logic to evaluate if the secondary database has a proper connection open.
I also tried to try/catch the connection issue with no success.
Thanks in advance for all the support you can offer.
Federico
It's because by setting the second parameter to TRUE (boolean) the function will return the database object and in the DB.php there is a function DB and the last code block is
function &DB($params = '', $active_record_override = NULL)
{
// ...
$driver = 'CI_DB_'.$params['dbdriver'].'_driver';
$DB = new $driver($params);
if ($DB->autoinit == TRUE)
{
$DB->initialize();
}
if (isset($params['stricton']) && $params['stricton'] == TRUE)
{
$DB->query('SET SESSION sql_mode="STRICT_ALL_TABLES"');
}
return $DB;
}
So, I think, if you call this
$other_db = $this->load->database($db,TRUE);
wiuthout the TRUE
$other_db = $this->load->database($db);
Then it could give you a different result.
Update : if i you want to use
$other_db = $this->load->database($db,TRUE);
then you can also check for a method availability using method_exists function, like
$other_db = $this->load->database($db,TRUE);
if( method_exists( $other_db, 'method_name' ) ) {
// ...
}
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.
I have project developed using cakephp which is getting data from different DBs, but if one of theses database some pages not open and give me the following error :
Database table tablenae for model moedlname was not found.
..and I have in this page other data displayed from the other database which work probably.
how i can determine if database is offline using cake and i can make this model read from another place like a cache file until the database startup again.
Perhaps a better approach is to cache results and read from the cache, only hitting the DB when needed...
<?php
$cacheKey = 'myCacheNumber1';
if (($data = Cache::read($cacheKey)) === false) {
$data = $this->Model->find('all');
if ($data) {
Cache::write($cacheKey, $data);
}
}
?>
The problem with this is it assumes the model and database connection are available for the time when the cache doesn't exist (or has expired), and if it wasn't, you'd still get the same errors, but the frequency would certainly be reduced.
I think to test if the DB is available at all would require some custom code trickery since the cake core method of connecting assumes success and fails heavily when not available. I'd probably make a component with standard PHP connect methods to control if you should attempt to load a model.
<?php
$cacheKey = 'myCacheNumber1';
if (($data = Cache::read($cacheKey)) === false) {
if ($this->DbTest->check('hostname','username','password')) {
$data = $this->Model->find('all');
if ($data) {
Cache::write($cacheKey, $data);
}
}
}
?>
<?php
// app/controllers/components/db_test.php
class DbTestComponent extends Object {
function check($hostname,$username,$password) {
$result = true;
$link = #mysql_connect($hostname,$username,$password);
if (!$link) {
$result = false;
}
#mysql_close($link);
return $result;
}
}
?>