I want a shell script I've got to be able to access appController, is this possible, without copying that code into the AppShell ? If so any pointers ?
Thanks in advance
In general, shells shouldn't rely on code inside a controller, some pointers (as requested):
If the Shell has to perform data-related tasks, move the code to the Model. This is good practice in any case (look up 'Skinny Controllers, Fat Models' on Google)
Although not 'standard' supported, you can move the code to a 'Component' (see Components). Components are meant for re-usable portions of code used in Controllers.
If the above options really aren't an option, you will have to manually initialise the AppController. keep in mind that, because you're running from the command line, various things will not be present, e.g. There will be no 'request' and some environment variables (e.g. host name) may not return the expected value!
Manually initialising a Controller
Manually initialising the controller will be something like this;
App::uses('CakeRequest', 'Network');
App::uses('CakeResponse', 'Network');
App::uses('Controller', 'Controller');
App::uses('AppController', 'Controller');
// request/response may be optional, depends on your use
$controller = new AppController(new CakeRequest(), new CakeResponse());
$controller->constructClasses();
$controller->startupProcess();
Related
I started not long ago with building my own mvc structure in PHP.
I have seen many people include in home.php page the header.php and footer.
I am currently stuck in finding a solid way to render my views.
I would like to know if it is even possible the way I am combining the header.php, home.php, and footer.php because this is not working for me which made me curious if there is even a native clean way of working with PHP layout structures?
any info would be appreciated. I try my best to explain the code below.
now working with this MVC structure. the router currently checks the req url and gives the controller
example of very basic router:
2 parameters: first the route, second the controller witch should render the view.
public function get($route, $controller) {
if($_SERVER['REQUEST_METHOD'] !== 'GET') {
return false;
}
$uri = $_SERVER['REQUEST_URI'];
if($uri === $route) {
$this->handled = true;
return include (controllers . $controller);
}
}
the routes that are being called:
$router = new Router();
$router->get('/', 'home.contr.php');
$router->get('/home', 'home.contr.php');
$router->get('/about', 'about.contr.php');
$router->get('/portfolio', 'projects.contr.php');
the router calls the controller and in my controller I render the view. with CreateView function
Home.contr.php:
class Home extends Controller {
public function __construct() {
Home::CreateView('home');
}
}
$home = new Home();
the extend controller that should implement the views/layout:
class Controller {
public static function CreateView($viewName) {
require_once views . 'components/header.php';
require_once views . "$viewName.php";
require_once views . "components/footer.php";
}
}
thank you in advance.
Limitations
MVC is a very broad topic with much different understanding so that by the term alone this is hard to answer properly - even in context of a PHP application. You normally refer to an existing implementation of MVC which is not the case here as you want to do it your own (Hint: Read code of existing implementations that is available and about you want to learn more).
Discussion
With that being said, you can find some practical "first next steps" suggestions at the end of the answer.
But I read your question as well that you're concerned about the HTML templates and perhaps also what this has to do with how you wrote your example. So I start a non-binding discussion about the View and then go over to Route and Controller. The Model layer I've kept out of the discussion mainly, at least for that you have to face third-party libraries as otherwise your application structure would not be a good host for broad functionality, this is touched by autoloading.
I have no authority in MVC, I just used some of the early implementations in PHP and applications influenced by them but never implemented it fully. So don't read out any suggestion from the discussion regarding it, it is merely about your example and what came to my mind in specific to PHP. At the end of the day it is you who will find the answer to your own programming questions.
Let's go.
A suggestion/assumption first: You certainly don't want to implement the view creation with the Controller class but with a View class. It would not change much just that the controller does not "care" about it (MVC = Model View Controller).
You can refactor (change) your code by introducing a View class and move the Controller::createView() to View::create() (compare: extract/move method).
Then using require_once - while it may work - it would only work if the template file is only used once. This is certainly not what you want to express here (and later in the discussion we'll see that with the existing example this can also more easily happen than perhaps intentionally thought), instead use require (or include depending on how you want to handle errors) as they will always execute the code in the file (for potential problems redefining controllers, see later in the discussion first routing and then second autoloading).
Apart from obvious code errors (typos) you'd need to address to get it to run (which is a good opportunity to explore PHP error handling and monitoring for your application) you still need to pass the output data of the controller to the view.
This can be so called view models or just objects (in the broader sense) holding the data to be viewed (rendered by the view). Just require/include-ing the (HTML layout) template files won't suffice as they may contain the HTML structure but not the controllers' output data. On the level of the templates this is typically in variables, e.g. the title of the hypertext document:
<title>
<?= htmlspecialchars($title, ENT_QUOTES | ENT_HTML5) ?>
</title>
If this would be the body of a function, the function definition would be:
function outputHeader(string $title): void {
# ...
}
As we don't have a function by requiring the template files, this is just exemplary. However we could create a generic function that handles requiring a template file and passing the variables to the template (compare include_helper()). In that layer you can also do some ground level error handling (try {} catch (Throwable $throwable) {} etc.). For starters you could collect and group such code in the View class.
What you also likely want to prevent is to bind the view within the controllers' constructor method (Controller::__construct(), ctor in short). It forces you to have a named view - and always the same - makes the controller dependent on that view.
That would mean you couldn't configure any view to any controller. While it wouldn't make sense in most cases to allow an any-to-any relationship here in the concrete practice, it allows you to actually have layer boundaries and to not couple things too tightly (compare: Spaghetti Code 1) and to write code on a higher level (in grade of abstraction, compare Layer of Indirection).
An example in a HTTP application would be to do content negotiation. This would happen on the level of request processing (more in the Router in your example), e.g. a HTTP client requests JSON instead of HTML. Now the HTML templates wouldn't fit here. But the Controller could still do the work if not the view template would be hard-encoded.
To keep things more flexible (so you can use it to a greater extend), one benefit of the MVC model is to use (and to a certain degree somehow pass the result of) the Model by the Controller to the View. It helps you define clear boundaries between those three and keep them more apart from each other (less coupled).
The routing then could negotiate and decide what to bring together, similar as in your example for the Controller already but extended with the View (template), each route could be assigned a layout/template.
As this would work quite the same as with the controller - just for the view - let's see where the current Controller not only is standing in the way for the view but already for the routing (if you find a flaw or bug, look around, often they are not in a single place and alone).
While you already configure the routes in the router, the actual routing you've put in the Controller base-class (Controller::get($route, $controller)). Similar to the __construct() method, this makes the Controller implementation dependent on the Route and even implements the routing. This is pretty convoluted and will certainly become awkward. There is also the problem when you add more routes you loose control which one matches as the matching is done within each Controller etc. . In short, while the code may be functional, it just seems to me it can benefit to be at a different place. As it's about the routing, first place that comes into my mind would be the Router itself. The Router then could do the actual work, "do the routing":
$router = new Router(); # <-- bootstrap
$router->get('/', 'home.contr.php'); # <-- prepare
$router->get('/home', 'home.contr.php'); # <-- prepare
$router->get('/about', 'about.contr.php'); # <-- prepare
$router->get('/portfolio', 'projects.contr.php'); # <-- prepare
$router->route(); # <-- do the work here
The Routers get() method then could stay the same from the outside but you would just store the routes inside and when you invoke the route() method, that configuration is matched against your request implementation.
You could then extend the router configuration with the view name.
It would be then that you still have bound a route to a controller and a view, however you have a central location where this is done (configured/parameterized). Controller and View are more independent to each other and you can concentrate more with their own implementation than the overall wiring which now moved into the router.
Finally while being here, what your example also shows is its dependence on the file-system, you have a certain file-naming convention for the controllers and also the view templates. While it is implicitly necessary to place the code into files, at least in your example on the level of the controllers you can already rely on PHP autoloading. While you want to write everything yourself (e.g. not using a ready-made MVC library), I'd still suggest to make use of some standards, like Autoloader (PSR-4) and as being inherently lazy, make the app a Composer project (it has a composer.json file) as Composer allows you to configure the autoloader and there is a well-defined process developing with it (you can also bring in more easily third-party libraries which you'll certainly need within your application logic, so this is just forward-thinking in a good sense, just start without any requirements just using the Composer autoloader).
So instead of hard-linking controller PHP file-paths, you could say instead that a controller basically is a class definition with at least a single method that the router is able to call. With the autoloader in action, the routing configuration would only need to reference that class/method and PHP then would take care to load the class. This could be done as strings (lazy-loading) or more explicit with the First class callable syntax (PHP 8.1). A good middle-ground for starters perhaps is to have one Controller per class and require to have it an interface so that you have a contract (compare: programming against interfaces 1, 2, 3, 4, 5, 6, 7 etc.). You can then simply pass the class-name and handle the instantiation in the route() method.
$router->get(
/* route */ '/',
/* $controller */ MyApp\MVC\Crontroller\Home::class,
/* $viewName */ 'home'
);
<?php
namespace MyApp\MVC\Controller;
class Home implements Interface {
# ...
}
<?php
namespace MyApp\MVC\Controller;
interface Interface {
public function invoke(InputParameter $params): InvocationResult
}
The route() then could check for the interface to verify some class can be used as a controller (instanceof) and would know how to invoke() the controller by passing the input parameters to receive the result that can be further delegated to the template layer.
This is made possible by also introducing the InputParameter and InvocationResult implementations (classes/interfaces) that help to define the layer boundary of the Controller part.
You can then do something similar for the View layer however the output comes relatively late and you're perhaps not yet settled with it (and you may have different template "engines" depending on use-case) so I would leave it more thin and less engineered and try with the Controllers first and do the delegation in the routing until you learn more about your actual requirements (Session handling, Authentication, Content-Negotiation, Redirects etc.).
At the end of the day you have to make your own decisions here.
Next Steps Suggestions
Add at least one test-script that you can run from your development environment "with a single key-press / click" and simple OK/Fail result (e.g. a simple PHP script that you execute in the shell)
Think about how to improve the error handling so you learn about defects faster (e.g. introduce exception and
Fix the bugs first, your code should actually run first of all (it might not produce the intended results in full but it should at least run - your example does not)
Init Composer / add composer.json to your project
Then change the code to your liking which can benefit having it under test first (compare Unit Tests)
I created a simple test for my new Laravel 7 application. But when I run php artisan test I get the following error.
Target [Illuminate\Contracts\View\Factory] is not instantiable.
The error doesn't appear when I go to the page in the browser.
$controller = new HomeController();
$request = Request::create('/', 'GET');
$response = $controller->home($request);
$this->assertEquals(200, $response->getStatusCode());
Although "Just write feature tests" may seem like a cop-out ("They're not unit tests!"), it is sound advice if you do not want to get bogged down by framework-specific knowledge.
You see, this is one of those problems that come from using facades, globals, or static methods. All sorts of things happen outside of your code (and thus your test code) in order for things to work.
The problem
To understand what is going on, you first need to know how Laravel utilizes Containers and Factories in order to glue things together.
Next, what happens is:
Your code (in HomeController::home() calls view() somewhere.
view() calls app() to get the factory that creates Views1
app() calls Container::make
Container::make calls Container::resolve1
Container::resolve decides the Factory needs to be built and calls Container::build to do so
Finally Container::build (using PHP's ReflectionClass figures out that \Illuminate\Contracts\View\Factory can not be Instantiated (as it is an interface) and triggers the error you see.
Or, if you're more of a visual thinker:
The reason that the error is triggered is that the framework expects the container to be configured so that a concrete class is known for abstracts (such as interfaces).
The solution
So now we know what is going on, and we want to create a unit-test, what can we do?
One solution might seem to not use view. Just inject the View class yourself! But if you try to do this, you'll quickly find yourself going down a path that will lead to basically recreating loads of framework code in userland. So not such a good idea.
A better solution would be to mock view() (Now it is really a unit!). But that will still require recreating framework code, only, within the test code. Still not that good.[3]
The easiest thing is to simply configure the Container and tell it which class to use. At this point, you could even mock the View class!
Now, purists might complain that this is not "unit" enough, as your tests will still be calling "real" code outside of the code-under-test, but I disagree...
You are using a framework, so use the framework! If your code uses glue provided by the framework, it makes sense for the test to mirror this behavior. As long as you don't call non-glue code, you'll be fine![4]
So, finally, to give you an idea of how this can be done, an example!
The example
Lets say you have a controller that looks a bit like this:
namespace App\Http\Controllers;
class HomeController extends \Illuminate\Routing\Controller
{
public function home()
{
/* ... */
return view('my.view');
}
}
Then your test[5] might look thus:
namespace Tests\Unit\app\Http\Controllers;
use App\Http\Controllers\HomeController;
use Illuminate\Contracts\View\Factory;
class HomeControllerTest extends \PHPUnit\Framework\TestCase
{
public function testHome()
{
/*/ Arange /*/
$mockFactory = $this->createMock(Factory::class);
app()->instance(Factory::class, $mockFactory);
/*/ Assert /*/
$mockFactory->expects(self::once())
->method('make')
->with('my.view')
;
/*/ Act /*/
(new HomeController())->home();
}
}
A more complex example would be to also create a mock View and have that be returned by the mock factory, but I'll leave that as an exercise to the reader.
Footnotes
app() is asked for the interface Illuminate\Contracts\View\Factory, it is not passed a concrete class name
The reason Container::make does nothing other than call another function is that the method name make is defined by PSR-11 and the Laravel container is PSR compliant.
Also, the Feature test logic provided by Laravel already does all of this for you...
Just don't forget to annotate the test with #uses for the glue that is needed, to avoid warnings when PHPUnit is set to strict mode regarding "risky" tests.
Using a variation of the "Arrange, Act, Assert" pattern
This is not how you test endpoints in Laravel. You should let Laravel instantiate the application as it is already setup in the project, the examples you can see here.
What you already wrote can be rewritten to something like this.
$response = $this->call('GET', route('home')); // insert the correct route
$response->assertOk(); // should be 200
For the test to work, you should extend the TestCase.php, that is located in your test folder.
If you're finding this in The Future and you see #Pothcera's wall of text, here's what you need to know:
The ONLY reason he's doing any of that and the ONLY reason you're seeing this in the first place in a Unit test is because he and you haven't changed from PHPUnit\Framework\TestCase to Tests\TestCase in the test file. This exception doesn't exist when you extend the test case that includes app().
My advice would be to simply extend the correct base test case and move on with your life.
I'm running some unit-tests using PHPUnit, and hack my application pretty hard in those tests (no other way, old code-base). Some parts of the code-base use
Yii::app()->getController()->createUrl(...);
but in this case, there is no controller, so the test fails. Is there a way to add a dummy controller dynamically in my test? Something like
Yii::app()->setController($dummyController);
Or do I have to initiate some kind of fake routing event?
You can simply use:
$ctrl = new CController('whatever you need for the id')
and use its methods. Be careful, construct method sets id only. You didn't provide too much code, so this is a general idea. Look inside createUrl() method and check if it should work.
I used this technique to render pages (and use their contents) under console enviroment.
If you need a controller for multiple tests you can set it once in the setUp method.
public function setUp()
{
parent::setUp();
Yii::app()->controller = new CController('test');
}
And then you can use it in your tests:
Yii::app()->controller->createUrl(...)
I am having a controller IndexController.php in which action is something like this
class IndexController extends CustomControllerAction {
public function preDispatch() {
if (!$this->view->authenticated) {
$this->_redirect('/users/login');
}
}
public function indexemailAction() {
//somecode which calculates certain things
}
}
NOw,I need to call the action "indexmailAction" inside the IndexController.php with an independent php file
The php file is indextest.php
<?php
//Need to write some code to call indexmailAction in IndexController.php
?>
What should I write in this file ......
Thanks in advance
I know this is a few years old, and this may not be the intended use of the classes/functions, but I've found the following quite useful in isolated files that are called from the command line.
The problem this solves for me is that it eliminates spawning of Apache processes. The solution is great because I can access the some Controller/Action needed that I would from the URL.
In almost any ZF1 based app, you can copy your index file and keep everything the same and just comment out the following line.
$application->run();
Anything below this line you can access with your autoloaders etc. It's crude, but it works. Unfortunately, you'll soon find yourself with limited access to a lot of the files your application has, and the feeling the only way you can access the files needed is through a Controller/Action.
Instead, I use the following in a new file below $application->bootstrap() ( still removing the $application->run() ):
$front = Zend_Controller_Front::getInstance();
// You can put more here if you use non-default modules
$front->setControllerDirectory(array(
'default' => APPLICATION_PATH.'/controllers'
));
$viewRenderer = new Zend_Controller_Action_Helper_ViewRenderer();
$viewRenderer->setNeverRender(true);
Zend_Controller_Action_HelperBroker::addHelper($viewRenderer);
$req = new Zend_Controller_Request_Http("http://anydomain.tld/controller/action");
// Example just to see how this can be extended
$req->setParam("someVar", "someValue");
$front->setRequest($req);
$front->dispatch();
In the end you have a isolated PHP file that bootstraps everything the same as your main index.php for the web, but you can manually trigger a controller/action as needed, giving you easier access to the rest of the files with how ZF1 intended you to access them.
Controllers are designed to be used in an MVC, not by scripts. Your controller should assemble request variables, direct them to models and return an HTTP response of some sort. Your scripts should act directly on the models instead.
Anyhow, if you insist, you can instantiate a controller class and call methods just like any other class as long as you inject any dependencies that the MVC would have.
If you want logic used in multiple places in your actions, then it should go in an action helper or if very generic code, then in a custom library (/library/custom/)
NB: The authentication would be better suited in a plugin rather than the pre-dispatch method in every controller.
You should not have to call a controller action for this, your logic should reside in your models. Then you can create a new instance of your model and invoke the appropriate methods. example :
require_once '/path/to/mymodel.php';
$mymodel = new Mymodel();
$data = $mymodele->fetchAll();
PS: Maybe you should think of creating a restful api to handle calls from outside your application
UPDATE:
ok, I see now what you need, The best way to achieve it is to call a url instead of a file (e.g. website.com/emails/send), if you are worried about security you can use some preshared key to make sure the request comes from you, send it with the request and check if it's correct in your action.
I wrote a shell method in CakePHP 1.3 that has a return value.
I'd like to be able to access that method from within a controller, so that I can pass its return value into the View.
I'm not sure how to access those methods appropriately from within the controller. Have I done it wrong?
I could easily duplicate the code, but I'd like to "keep it DRY", and the actual functionality, I believe, doesn't belong with this particular controller - I just need it's return value in this particular view.
EDIT:
I realize I'm sort of asking the wrong question here, since the Shell itself shouldn't necessarily return a value. I've changed the code so that the Shell is only using the return value I want, and now I wonder - what is the appropriate place for extra classes/code that needs to be accessed from a Shell and a Controller?
It seems like Component code, but I'm not sure how to access Components from the Shell. It's not a Plugin, as I understand them. Where does this go?
In one of the projects we imported shell tasks, ex:
App::import('Core', 'Shell');
App::Import('Vendor','shells/tasks/sometask');
$returndata = TasknameTask::execute($somevalue);
You can create a component to do that.E.g
/* in app/controllers/components */
class ShellComponent extends Object
{
function do_shell()
{
return shell_exec('some command');
}
}
Then use it in any controller you want as below
/* in some controller*/
var $components = array('Shell','maybe some other components',....);
function testShell()
{
$result = $this->Shell->do_shell();
....
}
Shells shouldn't directly return a value explicitly, they ought to report it somehow, e.g. by echoing it to stdout, logging to a file or sending an email for example. I like to think of shells as controllers for the cli.
Without knowing your application, my suggestion would be to see if you could refactor the logic in your current shell into a model class or something like that, have the model method return the value, then use that model in your shell. This way, you can also use that model in your controller.
The accepted answer doesn't seems to work for Cake 2.0
For Cake 2.0
if (!class_exists('Shell')) {
require CONSOLE_LIBS . 'shell.php';
}
App::import('Shell', 'DoSomething');
DoSomethingShell::main();
Bear in mind some Shell method doesn't work in this mode, such as $this->out, so more hacking is required.