So i am experiencing a problem I should probably only be having on big pages but instead im getting that on ANY page.
I am trying to make a REST api using CodeIgniter and it works but any page I load is very very slow. I am talking 20-30 seconds page loads for a page worth 504 Bytes.
I've added extra benchmarks and it displays it takes roughly 0.04 sec to download the data.
Rankings.php (Controller)
class Rankings extends CI_Controller
{
public function __construct()
{
parent::__construct();
$this->load->model('rankingsmodel');
}
/**
* Index Page for this controller.
*/
public function index()
{
$this->benchmark->mark('code_start');
$rankings =$this->rankingsmodel->getAllRankings();
$mappedRankings = array_map(array($this, 'mapRank'), $rankings, array_keys($rankings));
$this->benchmark->mark('code_end');
echo $this->benchmark->elapsed_time('code_start', 'code_end');
return $this->jsonIFy($mappedRankings);
}
public function mapRank($ranking, $k)
{
$ranking['rank'] = $k;
return $ranking;
}
public function jsonIFy(array $data = array())
{
echo json_encode($data);
}
}
And my rankingsmodel
class RankingsModel extends CI_Model
{
public function __construct()
{
parent::__construct();
}
public function getAllRankings()
{
$this->benchmark->mark('code_start2');
$query =$this->db->query('SELECT * FROM characters3 WHERE gm < 3 ORDER BY rebirths DESC')->result_array();
$this->benchmark->mark('code_end2');
echo $this->benchmark->elapsed_time('code_start2', 'code_end2');
return $query;
}
}
I have absolutely no idea why page loads so incredibly slow while my benchmarks are saying it would take 0.03/0.04 to load. I've tried setting up xdebug but i've not had any luck setting it up. Im using regular mysqli drivers that come with codeigniter.
Does anyone have any idea why it is so slow or any tool i can setup easily to debug?
Thank you
Okay so after a few more hours of digging i have found the problem.
The problem is the following:
My database was getting retrieved very slowly (we have a demo on db4free or smth) and that is what was taking so long. We also had $db['default']['pconnect'] set to false which meant we had to constantly reconnect to the database.
For testing purposes setting pconnect to true will solve the issue. And oh: get a faster database for production.
Related
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 am trying to test around Agile Toolkit with oracle, after seting up a model and trying to show a grid it says "No records found"...
Let me tell you what I did, since i've been guessing most of all configuration as I found no guides for oracle.
My Oracle connection string in agiletoolkit config-default.php file looks like this:
$config['dsn']= array( 'oci:dbname=localhost/MYDATABASE', 'MYUSER', 'MYPASSWORD' );
To fix the driver not found error, I enabled extension=php_pdo_oci8.dll in the php.ini file from my apache installation.
Then there was an error about a missing "oci.php", to solve that I had to create my own file like this:
class DB_dsql_oci extends DB_dsql {
function limit($cnt,$shift=0){
$cnt+=$shift;
$this->where('NUM_ROWS>=',$shift);
$this->where('NUM_ROWS<',$cnt);
return $this;
}
function render_limit(){
return '';
}
}
and placed it at: ...atk4\lib\DB\dsql
To fix the special chars error from oracle , I set line 59 on /atk4/lib/DB/dsql.php to empty string like this: public $bt='';
I manage to run the database test, and it says "Successfully connected to database."
Then I created a model "lib\Model\Mytable.php" like this:
<?php
class Model_Mytable extends Model_Table {
public $table = "MYTABLE";
function init(){
parent::init();
$this->addField('ID');
$this->addField('NAME');
$this->addField('INIDATE');
$this->addField('ENDDATE');
}
?>
After that, I made a new page and tried to use the model like this:
<?php
class page_test extends Page {
function init(){
parent::init();
$form = $this->add('Grid');
$form->setModel('Mytable');
}
}
?>
After refreshing the browser, it will show the grid saying " No Records found"
I wonder whats happening, that table has records no doubt, all data is committed, and im sure oracle is parsing queries because if I miss a column name an oracle error will raise.
Any clue?
This is how you can simply set DSQL as your View (Grid for example) data source:
class page_test extends Page_Basic
{
function init()
{
parent::init();
// DSQL
$q = $this->api->db->dsql();
$q->table('MYTABLE')
->field('DNAME')
->field('INIDATE');
// Create grid and set DSQL as its data source
$g = $this->add('Grid');
$g->addColumn('DNAME');
$g->addColumn('INIDATE');
$g->setSource($q);
// better add paginator too or your grid can become huge :)
$g->addPaginator();
}
}
I'm trying to run a job queue to create a PDF file using SlmQueueBeanstalkd and DOMPDFModule in ZF".
Here's what I'm doing in my controller:
public function reporteAction()
{
$job = new TareaReporte();
$queueManager = $this->serviceLocator->get('SlmQueue\Queue\QueuePluginManager');
$queue = $queueManager->get('myQueue');
$queue->push($job);
...
}
This is the job:
namespace Application\Job;
use SlmQueue\Job\AbstractJob;
use SlmQueue\Queue\QueueAwareInterface;
use SlmQueue\Queue\QueueInterface;
use DOMPDFModule\View\Model\PdfModel;
class TareaReporte extends AbstractJob implements QueueAwareInterface
{
protected $queue;
public function getQueue()
{
return $this->queue;
}
public function setQueue(QueueInterface $queue)
{
$this->queue = $queue;
}
public function execute()
{
$sm = $this->getQueue()->getJobPluginManager()->getServiceLocator();
$empresaTable = $sm->get('Application\Model\EmpresaTable');
$registros = $empresaTable->listadoCompleto();
$model = new PdfModel(array('registros' => $registros));
$model->setOption('paperSize', 'letter');
$model->setOption('paperOrientation', 'portrait');
$model->setTemplate('empresa/reporte-pdf');
$output = $sm->get('viewPdfrenderer')->render($model);
$filename = "/path/to/pdf/file.pdf";
file_put_contents($filename, $output);
}
}
The first time you run it, the file is created and the work is successful, however, if you run a second time, the task is buried and the file is not created.
It seems that stays in an endless cycle when trying to render the model a second time.
I've had a similar issue and it turned out it was because of the way ZendPdf\PdfDocument reuses it's object factory. Are you using ZendPdf\PdfDocument?
You might need to correctly close factory.
class MyDocument extends PdfDocument
{
public function __destruct()
{
$this->_objFactory->close();
}
}
Try to add this or something similar to the PdfDocument class...
update : it seem you are not using PdfDocument, however I suspect this is the issue is the same. Are you able to regenerate a second PDF in a normal http request? It is your job to make sure the environment is equal on each run.
If you are unable to overcome this problem a short-term quick solution would be to set max_runs configuration for SlmQueue to 1. That way the worker is stopped after each job and this reset to a vanilla state...
I hope someone can help me with this one before I jump off the window. I spent few hours on this one and don't know what am I doing wrong.
Basically, I've installed HMVC in CodeIgniter 2.1.2 and everything works fine, BUT for some reason I can't load models the same way I'm doing it in standard controllers. In the old codeigniter 1.7.1 I could use it simply by invoking $this->load->model('my_model') but now I can't?!
Every single time I'm trying to load model I get this error:
A PHP Error was encountered
Severity: Notice
Message: Undefined property: Special_cart::$db
Filename: core/Model.php
Line Number: 51
I have had installed it step-by-step according to the instructions. I got third_party next to modules folder. In modules I have few modules stored like this:
modules
--boxes
----controller
----models
----views
I invoke module in my code like this:
<?=modules::run('boxes/special_cart/index');?>
My module controller code looks like this:
class Special_cart extends CI_Controller
{
public function __construct()
{
parent::__construct();
}
public function index()
{
if ($this->session->userdata('cart'))
{
# get product id's and each quantity
$cart_product_list = array_count_values($this->session->userdata('cart'));
# get list of product_id
$product_list = array_keys($cart_product_list);
# get product details
$this->load->model('productmodel');
$this->load->model('stockmodel');
$cart_products = $this->productmodel->cart_get_products_details($product_list);
$final_cart_array = array();
foreach($cart_products as $cart_product){
$product_stock = $this->stockmodel->view_product_stock($cart_product["id"]);
if(empty($product_stock) || $product_stock["UNITS"]<=0)
$cart_product["UNITS"] = 0;
else{
if($cart_product_list[$cart_product["id_web"]]>$product_stock["UNITS"])
$cart_product["UNITS"] = $product_stock["UNITS"];
else{
$cart_product["UNITS"] = $cart_product_list[$cart_product["id_web"]];
}
}
$final_cart_array[] = $cart_product;
}
$refresh_cart_array = array();
foreach($final_cart_array as $cart_product){
for($i=1;$i<=$cart_product["UNITS"];$i++){
$refresh_cart_array[] = $cart_product["id_web"];
}
}
$this->load->view("special_cart",array(
'refresh_cart_array' => $refresh_cart_array,
'final_cart_array' => $final_cart_array
));
} else {
$this->load->view("special_cart",array(
'refresh_cart_array' => NULL,
'final_cart_array' => NULL
));
}
}
}
I've tried every possible solution found on internet - none of them work....
I hope you understand my problem but in case you need some further explanation please ask me. Can anyone help?
Looks like the model you're trying to load wants to connect to the db, but the database driver is not available. If you use database queries in your application, why don't you load the database driver automatically?
Just insert "database" in the "libraries" array in application/config/autoload.php file. Don't forget to insert your database credentials into application/config/database.php.
$autoload['libraries'] = array('database');
If you need database connection just in one single model, load it before trying to access database library.
$this->load->database();
Try loading the model stating the module name as follows
$this->load->model('module_name/productmodel');
Class Models extends MX_Loader{
function getUser($username){
$sql="SELECT * FROM user WHERE username = ? ";
return $this->db->query($sql,array($username))->row();
}
}
you must using extends MX_Loader because i don't know if using CI_Model the database core cant be load in Codeigniter,,,
Try to use extend MX_Controller class (not CI_Contoller like you are doing atm)
Based on what you have wrote in comment above, I figured that you tried to create new instance of DB in module (based on chrises comment).
Do it on constuctor of Special_cart
So update current construct to be like
public function __construct()
{
parent::__construct();
$this->load->database('default');
}
(I'm writing this from top of my head, so check the methods)
Now for sure db driver should be available in your models.
Regarding issue with HMVC I dont think there are any. I'm using HMVC for a while now, and I found no problems in it (working with databases)
I had the same problem and mistake. I missed to extend controllers to MX_Controller. So the solution would be to change CI_Controller to MX_Controller like this:
class Special_cart extends MX_Controller
{
public function __construct()
{
parent::__construct();
$this->load->model('productmodel');
$this->load->model('stockmodel');
}
public function index()
{
if ($this->session->userdata('cart'))
{
# get product id's and each quantity
$cart_product_list = array_count_values($this->session->userdata('cart'));
# get list of product_id
$product_list = array_keys($cart_product_list);
# get product details
$cart_products = $this->productmodel->cart_get_products_details($product_list);
$final_cart_array = array();
foreach($cart_products as $cart_product){
$product_stock = $this->stockmodel->view_product_stock($cart_product["id"]);
if(empty($product_stock) || $product_stock["UNITS"]<=0)
$cart_product["UNITS"] = 0;
else{
if($cart_product_list[$cart_product["id_web"]]>$product_stock["UNITS"])
$cart_product["UNITS"] = $product_stock["UNITS"];
else{
$cart_product["UNITS"] = $cart_product_list[$cart_product["id_web"]];
}
}
$final_cart_array[] = $cart_product;
}
$refresh_cart_array = array();
foreach($final_cart_array as $cart_product){
for($i=1;$i<=$cart_product["UNITS"];$i++){
$refresh_cart_array[] = $cart_product["id_web"];
}
}
$this->load->view("special_cart",array(
'refresh_cart_array' => $refresh_cart_array,
'final_cart_array' => $final_cart_array
));
} else {
$this->load->view("special_cart",array(
'refresh_cart_array' => NULL,
'final_cart_array' => NULL
));
}
}
}
this is also explained in the documentation
https://bitbucket.org/wiredesignz/codeigniter-modular-extensions-hmvc/
, here the quote:
Notes:
To use HMVC functionality, such as Modules::run(), controllers must
extend the MX_Controller class. To use Modular Separation only,
without HMVC, controllers will extend the CodeIgniter Controller
class. You must use PHP5 style constructors in your controllers. ie:
<?php
class Xyz extends MX_Controller
{
function __construct()
{
parent::__construct();
}
}
I have to display different views for mobile devices and I want to provide a simple JSON-API.
I wrote a little module for the Kohana Framework which loads different views depending on some circumstances, which should help me in this case: https://github.com/ClaudioAlbertin/Kohana-View-Factory
However, I'm not very happy with this solution because I can't set different assets for different device-types. Also, when I'd output JSON with a JSON-view, it's still wrapped in all the HTML-templates.
Now, I'm looking for a better solution. How do you handle different output formats or device-types in your MVC-applications?
I had an idea: just split the controller into two controllers: a data-controller and an output-controller.
The data-controller gets and sets data with help of the models, does
all the validating etc. It gets the data from the models and write it to a data-object
which is later passed to the view.
The output-controller loads the views and give them the data-object from the data-controller. There is an output-controller for each format or device-type: an output-controller for mobile-devices could load the mobile-views and add all the mobile-versions of stylesheets and scripts. A JSON-output-controller could load a view without all the html-template stuff and convert the data into JSON.
A little example:
<?php
class Controller_Data_User extends Controller_Data // Controller_Data defines a data-object $this->data
{
public function action_index()
{
$this->request->redirect('user/list');
}
public function action_list()
{
$this->data->users = ORM::factory('user')->find_all();
}
public function action_show($id)
{
$user = new Model_User((int) $id);
if (!$user->loaded()) {
throw new HTTP_Exception_404('User not found.');
}
$this->data->user = $user;
}
}
class Controller_Output_Desktop extends Controller_Output_HTML // Controller_Output_HTML loads a HTML-template
{
public function action_list($data)
{
$view = new View('user/list.desktop');
$view->set($data->as_array());
$this->template->body = $view;
}
public function action_show($data)
{
$view = new View('user/show.desktop');
$view->set($data->as_array());
$this->template->body = $view;
}
}
class Controller_Output_JSON extends Controller_Output // Controller_Output doesn't load a template
{
public function action_list($data)
{
$view = new View('user/list.json');
$view->users = json_encode($data->users->as_array());
$this->template = $view;
}
public function action_show($data)
{
$view = new View('user/show.json');
$view->user = json_encode($data->user);
$this->template = $view;
}
}
What do you think?
Hmm... From the 1st view it loooks strange, and somehow like fractal -- we are breaking on MVC one of our MVC -- C.
But why is this app returns so different results, based on point-of-entry (or device)?
The task of the controller is only to get the data and choose the view -- why do we need standalone logic for choosing something based on point-of-entry (device)?
I think these questions should be answered first. Somewhere could be some problem.
Also the cotroller should select only one view ideally, and dont' do "encode" or else with data, based on current output. I think all this should be in some kind of "layouts" or else. As data always the same and even different views should be the same -- only some aspects changes.