I'm trying to execute a Shell from my Controller via AJAX request.
In my controller :
public function log_import() {
$this->autoRender = false;
App::import('Console/Command', 'AppShell');
App::import('Console/Command', 'IzigetlogShell');
$job = new IzigetlogShell();
$job->dispatchMethod('main');
echo "REPONSE";
}
And my shell :
<?php
App::import('Core', 'Controller');
App::import('Controller', 'Suivis');
class IzilogShell extends AppShell {
public $uses = array('Suivi');
-- DU CODE --
$this->Suivi = new SuivisController();
$this->Suivi->constructClasses();
$exist_date = $this->Suivi->find('first',
array(
'conditions' => array('Suivi.date' => $date_calcul)
));
}
But i always get the same error message :
PHP Fatal error: Call to undefined method SuivisController::find() in C:\wamp\www\iziboxLogs\app\Console\Command\IzigetlogShell.php on line XX
I tried to execute the shell from the console and i got the same error.
Any ideas ? Thanks, Martin
The short answer is: You don't!
Its a violation of the MVC what cake stands for.
You need to move your "shared" code into a model and use this model only in both cases. not the controller (which is the link/logic from model and "webbrowser", not your shell). and not the shell from within the controller (since the shell is the link/logic from model and "CLI").
So:
Model contains all the code
Shell uses Model and its methods
Controller uses Model and its method
=> DRY and clean
then you would also need not a single App::import (or better App::uses) statement.
PS: if you happen to have a lot of non-model code, you can also make a Lib in APP/Lib and use this as your common class.
PPS: public $uses = array('Suivi'); is for models anyway, not for controllers (as the docs speficy by the way).
By doing this
$this->Suivi = new SuivisController();
this->Suivi becomes a SuiviController and not a Suivi model anymore.
But you then use it as a model:
$exist_date = $this->Suivi->find(...);
The find() method is a model method, not a controller method. So if you do need to call the find() method, there is no need to instanciate the SuivisController at all.
Related
In my CodeIgniter 2 controller I call a model method which returns a ReactPHP promise, and I want to load a CodeIgniter view in the function called by that promise's ->then() method. How can I do this? What happens instead is the controller method returns nothing, so I get a blank page in the browser.
Here is a simplified example illustrating what I'm trying to do:
class My_class extends My_Controller {
function my_method() {
$this->my_model->returns_a_promise()->then(function ($data) {
// How can I pass the promise's resolved value to the template here?
// It seems this never gets called, because my_method() returns
// before we get here. :(
$this->load->view('my_view', $data);
});
}
}
Is there any way to tell the controller method not to send output to the browser until after the promise has resolved?
I'm not sure what are you trying to do but if you want to stop view from outputting and return it as a string then output it with echo yourself you can do this:
$view = this->load->view('my_view', $data, TRUE);
Now you have the view as a var string you can use it to do what you are trying to do.
It turns out the code in my original question does work. So the question is the answer. But the reason it wasn't working for me was that returns_a_promise() was not returning a resolved promise, so ->then() was not called and the view was not rendered. In order to make it return a resolved promise, I had to call $deferred->resolve(); in the code that returned the promise.
The upshot of this is that this code example demonstrates it is possible to run asynchronous PHP (via ReactPHP in this case) in CodeIgniter controller methods. My particular use case is to run many database queries concurrently in the CodeIgniter model.
try this:
function my_method() {
$data = array();
$data['promise'] =$this->my_model->returns_a_promise();
$data['view'] = 'my_view';
$this->load->view('my_view', $data);
}
To use Emails, I just
use Cake\Network\Email\Email;
$email = new Email();
but trying that with UrlHelper is unsuccessful.
As unsuccessful is with a
$url_helper = new UrlHelper;
because it gives me
Argument 1 passed to Cake\View\Helper::__construct() must be an instance of Cake\View\View.
But I don't have View in Shell, I suppose.
Thanks.
Using a helper in the shell is not the right way to approach this. The helper as well is just wrapping the Router. So instead use the Router directly:
use Cake\Routing\Router;
$url = Router::url([/*...*/], true);
Also note that you can change the base URL as needed by calling Router::fullBaseUrl(), it's a getter and setter. See the documentation.
This is better because:
It's introducing less dependencies
Less tight coupling (OK, you get a singleton instead...)
Doesn't violate MVC
Smaller footprint
You could probably follow a similar method for mocking out a helper in the shell as they do for mocking out a helper for unit testing. Try:
<?php
use Cake\View\View;
use Cake\View\Helper\UrlHelper;
class FooShell extends Shell
{
public $url_helper = null;
public function bar()
{
$view = new View();
$this->url_helper = new UrlHelper($view);
// URL Helper functionality created here
}
}
Here is the docs I used for mocking out a helper in CakePHP 3.
Take a look to that code
<?php
namespace Sestante\SestanteBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Sestante\SestanteBundle\Model\StrutturaManager;
class MainController extends Controller
{
public function indexAction(Request $request)
{
return $this->render('SestanteSestanteBundle:Main:index.html.twig');
}
public function showLodgingsAction(Request $request)
{
$repo = $this->getDoctrine()->getRepository('SestanteSestanteBundle:Struttura');
$usr = $this->get('security.context')->getToken()->getUser();
$usrId = $usr->getId();
$sm = new StrutturaManager($repo);
$lodgingList = $sm->retrieveLodgingsFromUser($usrId);
return $this->render('SestanteSestanteBundle:Main:showLodgings.html.twig',array('lodgingList' => $lodgingList));
}
}
This is a controller for an application that I've been writing.
Take a look to showLodgingsAction. I've tryied to place all business logic into a model (StrutturaManager) that with a repository (that I've passed directly from controller because, as far I know, they're available only here or via D.I.) query my db, do some elaboration and return a list that I'll render onto a template.
First question: Is this "code separation" good, or exists a better way for do what I'm trying to do?
Second question: suppose that, now, I want to use an object of StrutturaManager type into indexAction. Remember that mine object want a repository. So, have I to declare again, and again, and again, all my object for every controller's action where I want to use them? I suppose that must exist a smarter method but, at the moment, I don't understand which.
Define StrutturaManager as a service and inject the EntityManager into it. This way the manager will have access to repositories you need and controllers won't know about Doctrine nor repositories — which is a good practice.
I'm having Codeigniter object scope confusion.
Say I load a model in a controller:
$this->load->model('A');
$this->A->loadUser(123); // loads user with ID 123
// output of $this->A now shows user 123
$this->load->model('B');
$this->B->examineUser ();
// output of $this->A now shows user 345
class B extends Model
{
public function examineUser ()
{
$this->load->model('A');
$this->A->loadUser(345); // loads user with ID 345
}
}
I would have thought that $this->A would be different from $this->B->A but they are not. What is the best solution to this issue? It appears the ->load->model('A') in the examineUser () method does nothing because it was loaded in the controller. Then the call to loadUser () inside that method overwrites the stored properties of $this->A. This seems like a bugfest waiting to happen. If I needed global models, I would have use static classes. What I wanted was something scoped pretty much locally to the model object I was in.
Is there a way I can accomplish this but not go way outside of CI's normal way of operating?
Followup/related:
Where do most people put there "->load->model" calls? All at the beginning of a controller action? I figured it would be easier -- though perhaps not excellent programming from a dependency injection perspective -- to load them in the model itself (construct or each method).
Whenever you use the Loader Class ($this->load->), it will load the object into the main CI object. The CI object is the one you keep referring to as $this->. What you've done is load model A twice into the CI object.
Essentially, all object loaded using the Loader class goes into a single global scope. If you need two of the same type, give them different names, as per $this->load->model('A','C'). I don't know of any way around it unless you revert to using bog-standard PHP.
In my team's code, we generally load the models in the controller's constructor, then load the data to send to the view in the function, often _remap().
This is not how the loader works sadly. CodeIgniter implements a singleton pattern, which will check to see if the class is included, instantiated and set to $this->A then will be ignored if loaded again. Even if you are inside a model, $this->A will be referenced to the super-instance via the __get() in class Model. Alis it, or just do:
class B extends Model
{
public function examineUser ()
{
$user = new A;
$user->loadUser(345); // loads user with ID 345
}
}
Here's what I've decided to do, please comment if you have advice:
I've extended the CI Loader class:
<?php
class SSR_Loader extends CI_Loader
{
function __construct()
{
parent::__construct ();
}
/**
* Model Retriever
*
* Written by handerson#executiveboard.com to create and return a model instead of putting it into global $this
*
* Based on original 2.0.2 CI_Loader::model ()
*
*/
function get_model($model)
{
if (empty ($model))
{
return;
}
$name = basename ($model);
if (!in_array($name, $this->_ci_models, TRUE))
{
$this->model ($model);
}
$name = ucfirst($name);
return new $name ();
}
}
Do any CI guru's see a problem with that before I invest time in changing my code a bit to accept the return obj, ala:
// in a controller:
public function test ($user_id=null)
{
$this->_logged_in_user = $this->load->get_model ('/db/users');
$this->_viewed_user = $this->load->get_model ('/db/users');
$this->_logged_in_user->load($this->session->userdata ('user.id'));
$this->_viewed_user->load($user_id);
}
I could also do private $_logged_in_user to make it available in the controller but positively force it to be limited to just the current controller and not spill anywhere else, or I could just do $_logged_in_user = $this->load->get_model ('/db/users'); and limit it to just the current method, which is probably what I'll do more often.
This seems like a pretty straightforward way to "fix" this issue (I say "fix" b/c it's not really a bug, just a way of doing things that I think is a bad idea). Anyone see any flaws?
I'm passing a user object from the controller to the view, then calling a method on that controller. I've done a print_r on the object in the view, so I know it's the right object with the right values. The current_user variable is an instance of the user class.
Here is the line in the layout that gives the error.
<?php echo $this->current_user->get_avatar_url(); ?>
Here is the method in the user class it's calling
public function get_avatar_url()
{
return !empty($this->avatar) ? $this->avatar : $this->fb_userid != '' ? "http://graph.facebook.com/".$this->fb_userid."/picture" : "/public/images/pukie.jpg";
}
This is the error I get
Fatal error: main() The script tried to execute a method or access a property of an incomplete object. Please ensure that the class definition "User" of the object you are trying to operate on was loaded before unserialize() gets called or provide a __autoload() function to load the class definition in /home/breathel/public_html/application/views/layouts/layout.phtml on line 48
I'm including the full controller base where this in called in case it makes a difference
<?php
Zend_Loader::loadClass('Zend_Controller_Action');
Zend_Loader::loadClass('User');
class BaseController extends Zend_Controller_Action
{
protected $auth;
protected $current_user;
protected $db;
protected function initialize_values()
{
$auth = Zend_Auth::getInstance();
if($auth->hasIdentity())
{
$this->current_user = $auth->getIdentity();
$this->view->current_user = $this->current_user;
}
$this->db = Zend_Registry::get('dbAdapter');
$this->view->controller_name = $this->_request->getControllerName();
$this->view->view_name = $this->_request->getActionName();
}
}
Zend Framework's authorisation module uses sessions to preserve identity across page load and is probably serialising the User model under the covers (especially if you're just assigning the result of a Zend_Auth_Adapter call).
Try including the User class before the first call to getIdentity() and see if that fixes it (even if you're confident you're not serialising it yourself).