I have been running into this very weird issue with Laravel.
I had a problem where one of my component views was not able to read the variables defined in its class.
It was kind of strange because I have several components running in my project and they all worked fine, except for this one.
So I created a fresh Laravel project to test some things out (Wanted to check if the problem was on my end, maybe I somehow messed up the project files).
I created a new component on a blank project using php artisan make:component Test
Then I simply added a test variable to the class component like so:
<?php
namespace App\View\Components;
use Illuminate\View\Component;
class Test extends Component
{
public $test;
/**
* Create a new component instance.
*
* #return void
*/
public function __construct()
{
$this->test = "testing";
}
/**
* Get the view / contents that represent the component.
*
* #return \Illuminate\View\View|string
*/
public function render()
{
return view('components.test');
}
}
And tried to access it over in the view like so:
<div>
<p> {{$test}} </p>
</div>
For some reason, this is not working and I can't figure out why. It just says that $test is undefined.
Perhaps I should point out, I am a beginner in Laravel, so excuse me if I am making some obvious mistake. It just seemed weird that this is not working on a blank project.
Thank you in advance.
In fact, all of the answers are misleading. The $test property is already public, and as such available to the template. Please don't go and manually add parameters to the view.
I just encountered a similar issue, and while I can say for sure that it has to do with the component class name, I could not trace it down, because it started working again magically.
What I can say about it is:
I had a class with the same base classname but a different namespace, deleted the non-component class, and then suddenly the component was rendered as an anonymous component, by-passing the component class completely. (You can easily verify that by putting a dd() in your component class constructor.)
My first thought was that the autoloader needed a refresh, but composer dump did not change anything.
renaming the component class and template to something else solved the issue.
when I wanted to track down the bug, I renamed the class and template back to the original name, but now it suddenly worked...
my guess is that the view cache was causing the issue. It worked from the moment when I was changing the x-blade (<x-component-name ... />) in the template that was including the component. So it is plausible that the issue magically went away because the cached view was replaced due to the template change.
So based on this, your best options to solve the issue are:
Verify that your properties are public, they will not be available in your template if they are protected or private.
Clear caches, esp. the view cache (php artisan view:clear).
Dump the autoloader (composer dump).
Verify that your class is actually used. Put a dd() in your constructor. If you still get errors from the template, Blade is by-passing your class and trying to use your template as an anonymous component.
Rename the component to something else to see if the class name clashes.
Hope that helps anyone who experiences this confusing issue.
In my case It was because of cached view so php artisan view:clear did the job
so what is wrong?
The problem happens when you already have a view component and in some point you want to add a dedicated class to provide some data to your component and because laravel already cached your component the "dedicated class" wont be triggered
You need to send variable to the view. Some ways
In an array
$test = 'Hi';
return view('components.test', ['test' => $this->test]);
Using with
$test = 'Hi';
return view('components.test')->with('test', $test);
The last one, i like it this one.
$test = 'Hi';
return view('components.test', compact('test'));
Let me know how it works, regards.
within the render() function, instead of return view('components.test');, do return view('components.test', ['test' => $this->test])
Related
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've got something that's strange for me. It's in Symfony Controller, but I don't think that matters. In the controller file, next to controller class I created a simple "class" with consts just to keep some things in queries more clear:
class ExportType
{
const EXPORT_WORLDWIDE = 1;
const EXPORT_EU = 2;
}
Of course there's only one namespace at the top. These constants are used in one of the controller's actions.
Every time I use PhpStorm's code autoformatting, that class is moved to the top of the file. OK, I don't mind and PhpStorm doesn't report any error here. But seems like PHP (or Symfony?) doesn't like it, because every time that helper class is on the top, there's a FileLoaderLoadException thrown, saying that class doesn't exist in this namespace.
When it's at the bottom, there's no problem. Is it normal? Should used class be declared after the class that is using it??
The problem probably comes from Composer, you code is simply not PSR-0/4 compliant which is convention Composer is using to autoload your files. You get an error because it can't find the file to load because of that.
It probably works if declared as second because the only place where you are using it is in your Controller. Use your constant somewhere else and it will automatically fail.
I am new to Laravel, so still getting used to the concepts, however I have around 10 years of experience using Smarty. So I wish to capitalize on that (apart from the fact that Blade seems to lack too many features I find useful and ready out of the box in Smarty, but anyway besides the point of this question).
I have been looking around to find the right way of using Smarty in Laravel, and although on a few forums such as here it seems to be possible its not clear what I need to do to use it properly within the framework. More concretely my questions are:
Which Composer package should I include in my composer.json? There seems to be this which includes Smarty itself, because it was modified (not too keen about that). And here they also suggest http://bundles.laravel.com/bundle/SmartyView. Not sure if it is the same, because the bundles sub-domain of laravel.com isn't even coming up. It used to come up a few days ago, but I don't know if they turned it off because bundles are now obsolete and replaced by packages... not sure. There is also this
How should I configure Laravel to use Smarty views instead of Blade views?
Given that Laravel uses REST style pretty URLs, how should I include CSS and JS files from Smarty
views, such that the path to them is dynamically set by Laravel? In Blade you do something like: {{ HTML::style('css/style.css') }}. Can I use something similar? Or better still set a template variable from the code by calling the HTML class? (I don't really like calling PHP code within templates that should just be doing presentation logic.)
Sorry if some questions are a bit trivial.
OK so after some more research, I managed to painlessly integrate Smarty 3 within Laravel 4. I am not sure if it is the best way, but its working perfectly, so open to comments or further suggestions.
I installed this Smarty View for Laravel and followed the instructions to add it in the list of providers in app/config/app.php. After running the php artisan config:publish latrell/smarty command the configuration was automatically created and I was ready to go. This package also seems to use the proper Smarty libraries rather than some modified templates.
I then just created a plain old HTML file, with a .tpl extension in the app/views directory, and a corresponding controller in the app/controllers directory, together with the corresponding route in routes.php and hey presto, it worked without a hitch.
I even modified the BaseController to maintain a common list of template variables (such as the CSS styles etc.) to inject in the HTML without putting ugly PHP code in the template. (I don't know if there's a better way to set them directly into the View from the BaseController rather than expecting the sub-class to pass them in the call to the make method, but I guess thats a different question.)
Code below for who might need it:
HelloWorld.tpl
<!doctype html>
<html lang="en">
<head>
<title>Hello World</title>
<meta charset="UTF-8">
{$style}
</head>
<body>
<div>
<h1>Hello {$name}</h1>
</div>
</body>
</html>
BaseController.php
class BaseController extends Controller {
protected $template_vars = array();
protected function addTemplateVar($key, $value)
{
$this->template_vars[$key] = $value;
}
/**
* Setup the layout used by the controller.
*
* #return void
*/
protected function setupLayout()
{
//not sure if I need this any more since I am using Smarty
if ( ! is_null($this->layout))
{
$this->layout = View::make($this->layout);
}
$this->addTemplateVar("style", HTML::style("css/bootstrap.css"));
}
}
HelloWorldController.php
class HelloWorldController extends BaseController
{
public function showHelloWorld()
{
$this->addTemplateVar('name', 'World!');
return View::make('helloworld', $this->template_vars);
}
}
In routes.php:
Route::get('helloworld', 'HelloWorldController#showHelloWorld');
Hope its useful for someone else.
I have a mobile site that I added detection to for iPhones and other iOS devices. The iOS page needs a different layout and views than the regular pages (which are actually for older mobile devices). So, I have some code that does mobile detection, that part was easy. What I'd like to do is make it so that Zend automagically finds and uses the correct layout and view when an iOS device is detected, but that has turned out to be surprisingly hard...
I needed it to be up and running ASAP, so I did a quick and dirty hack that worked: in each action function, I have a simple If statement that detects if the iOS boolean flag has been set (which happens in the controller's init), and if so, overrides the layout and view explicitly. Existing code (in the actions):
if ($_SESSION['user']['iPhone']) {
$this->_helper->layout->setLayout('osriphone'); // 'osr' is the name of the app
$this->_helper->viewRenderer->setRender('iphone/index');
}
So this works, but it's kinda ugly and hacky and has to be put in each action, and each action's Renderer has to be set, etc. I got to reading about the Zend ContextSwitch, and that seemed like exactly the kind of thing I should use (I'm still kind of new to Zend), so I started messing around with it, but can't quite figure it out.
In the controller's init, I'm initializing the ContextSwitch, adding a context for 'iphone' and setting the suffix to 'iphone', and now what I'd like to do is have a single place where it detects if the user is an iOS device and sets the context to 'iphone', and that should make it automatically use the correct layout and view. New code (in the controller's init):
$this->_helper->contextSwitch()->initContext();
$contextSwitch = $this->_helper->getHelper('contextSwitch');
$contextSwitch->addContext('iphone', array('suffix' => 'iphone'));
$contextSwitch->setAutoDisableLayout(false);
if ($_SESSION['user']['iPhone']) {
//$this->_currentContext = 'iphone'; // Doesn't work.
//$contextSwitch->initContext('iphone'); // Doesn't work.
//$contextSwitch->setContext('iPhone'); // Not the function I'm looking for...
// What to put here, or am I barking up the wrong tree?
}
I did some reading on the contextSwitcher, and it seems like there is a lot of stuff on, e.g. setting it to be specific to each particular action (which I don't need; this needs to happen on every action in my app), and going through and modifying all the links to something like /osr/format/iphone to switch the context (which I also don't really need or want; it's already a mobile site, and I'd like the layout/view switch to be totally transparent to the user and handled only from the backend as it is with my quick and dirty hack). These seem like basically an equal amount of code to my quick and dirty hack. So... Anyone have some suggestions? I'm really hoping for just a single line like "$contextSwitch->setContext('iphone');" that I could use in an If statement in my controller's init, but the Zend documentation is awful, and I can't seem to find any examples of people doing something like this on Google or SO.
Ok I think I figured out how to put this into a plugin:
The Plugin:
//This is my own namespace for ZF 1.x library, use your own
class My_Controller_Plugin_Ios extends Zend_Controller_Plugin_Abstract {
public function preDispatch(Zend_Controller_Request_Abstract $request) {
parent::preDispatch($request);
if ($_SESSION['user']['iPhone']) {
$this->_helper->layout->setLayout('osriphone');
$this->_helper->viewRenderer->setRender('iphone/index');
}
}
}
register the plugin in your application.ini
resources.frontController.plugins.ios = "My_Controller_Plugin_Ios"
I think that's all there is to it. Although you may want to look into the userAgent plugin
ContextSwitch operates off the "format" property in the request object (by default). You need to set it somewhere in your app
$requestObject->setParam('format', 'iphone').
I'd set it in a bootstrap, or more appropriately, a controller plugin, but where it goes really depends on your app.
I don't use Zend ContextSwitch so I can't really help there, but you could use some inheritance in your controllers to set all layouts in just a couple of lines. Even though it might still be classed as a "hack" it is a way better hack
Now whenever you execute a action Zend first fires a number of other functions within the framework first, such as the routing, the preDispatch, Action helpers and so on. It also fires a number of things after the action such as PostDispatch. This can be used to your advantage.
First create a controller called something like "mainController" and let it extend Zend_Controller_action and in this controller create a function called predispatch()
Second. Extend your normal controllers to mainController. Since we now have a function called predispatch() Zend will automatically fire this on every controller, and if you do your iPhone/iOS check there it will automagically be performed on every action on every controller, as long as you don't overwrite the method in your controller (you can make this method final to prevent this). You can offcourse use a multitude of different non-Zend functions and/or helpers within the mainctroller to make the code as compact and reusable as possible Se example code below:
<?php
/**
*Maincontroller
*/
class MainController extends Zend_Controller_Action
{
/**
* Predispatch function is called everytime an action is called
*/
final public function preDispatch(){
//for security reasons, make sure that no one access mainController directly
$this->request = $this->getRequest();
if (strtolower($this->request->controller)=='main')
$this->_redirect('/index/index/');
//Check for iPhone
if ($_SESSION['user']['iPhone']) {
$this->_helper->layout->setLayout('osriphone'); // 'osr' is the name of the app
$this->_helper->viewRenderer->setRender('iphone/index');
}
}
}
<?php
/**
*Othercontroller
*/
class OtherController extends MainController
{
/**
* The correct layout for IndexAction is already set by the inherited preDispatch
*/
public function indexAction(){
/* YOUR CODE HERE */
}
}
For a good overview of the dispatch process check these links (same picture in both):
http://nethands.de/download/zenddispatch_en.pdf
http://img.docstoccdn.com/thumb/orig/22437345.png
I recently discovered that view helpers seem to be unavailable when manually calling $view->render().
In this particular case, I've got a config view helper which I can easily call from within my view scripts like so:
$this->config()->some->param
I am now trying to send a mail and discover that the above does not seem to work when manually calling the render method:
/**
* Within these view scripts, $this->config() is called,
* which results in an empty object
*/
$mail->setBodyText($this->view->render('partials/invite/email/text.phtml'));
$mail->setBodyHtml($this->view->render('partials/invite/email/html.phtml'));
Am I overlooking something? Is this a bug or intended behaviour? Should I take another approach on manually rendering view scripts?
Thanks in advance.
Can we see a bit more code?
So far I've got this to work with manually rendered views.
$view->setHelperPath('/path/to/helper/class');
print $view->render('view.phtml');
This here is the class named FooBar.php within /path/to/helper/class
<?php
class Zend_View_Helper_FooBar extends Zend_View_Helper_Abstract {
public function fooBar()
{
return 'random string this will be the output';
}
}
Within view.pthml
print $this->fooBar();
Outputs
random string this will be the output