How to use namespace in laravel View? I mean I have three different folders admin, frontend and client in app/views folder.
If I want to load a partial template lets say from admin section views/admin/partials/flush.blade.php in views/admin/account/profile.blade.php I have to include it like:
#include('admin/partials/flush')
instead I want to just use
#include('partials/flush')
How can i do that?
You can extend blade and write a function that fits your needs. Like this:
Blade::extend(function($view, $compiler)
{
$pattern = $compiler->createMatcher('includeNamespaced');
$viewPath = realpath($compiler->getPath());
$parts = explode(DIRECTORY_SEPARATOR, $viewPath);
$viewsDirectoryIndex = array_search('views', $parts);
$namespace = $parts[$viewsDirectoryIndex + 1];
$php = '$1<?php ';
$php .= 'if($__env->exists(\''.$namespace.'.\'.$2)){';
$php .= 'echo $__env->make(\''.$namespace.'.\'.$2)->render();';
$php .= '}';
$php .= 'else {';
$php .= 'echo $__env->make($2)->render();';
$php .= '}';
$php .= '?>';
return preg_replace($pattern, $php, $view);
});
And then use it like you described but with includeNamespaced
#includeNamespaced('partials/flush')
If you want to you could also override #include by naming it createMatcher('include')
Note "your" #includeNamespaced / #include wont have the option to pass arguments to the view your including (second parameter)
A little tip: When you change the code inside Blade::extend you have to delete the cached views in storage/views for the changes to show up in your browser.
Related
I wrote function which changes values in config/app.php using file_put_content.
public function updateConfig($path, $key, $value)
{
$content = file_get_contents($path);
$pattern = "/'" . $key . "' => '[0-9]+'/";
$replacement = "'" . $key . "' => '" . $value . "'";
$content = preg_replace($pattern, $replacement, $content);
file_put_contents($path, $content);
}
$path = base_path() . '/config/app.php';
$key = 'version_number';
$value = '10';
$service->updateConfig($path, $key, $value);
$this->assertEquals(config('app.version_number'), $value);
How can I test it with changing config file?
I presume you want to change the config then use it.
There is a way you can achieve this without editing the config file. Note that, in order to speed up Laravel, configurations are cached and you need to run php artisan clear:cache or manually delete cache files.
If you want to change config on the fly. Change it as shown before using it.
Config::set('app.version_number', 10);
Unfortunately config(['key' => 'newValue']) doesn't work in a Dusk setup (for overriding a config value), presumably because it would change the config of the system running the test rather than the experience of the headless browser that gets opened to execute the flow.
And sometimes I can see no way around needing to temporarily change an env value for a certain Dusk test.
E.g. temporarily set QUEUE_DRIVER=sync when usually it is 'dusk-connection', but in one particular test, I need to check for values in the 'jobs' tables in the DB.
Before upgrading to Laravel >=5.8 (and therefore newer versions of DotEnv), I was able to use this function called within a Dusk test before $this->browse(...:
/**
* Overrides any .env variables for Dusk tests. https://laracasts.com/discuss/channels/testing/how-to-change-env-variable-config-in-dusk-test
* The changes exist only for that one test because of tearDown.
* Remember that you need to be using `php artisan dusk` instead of `phpunit`.
* https://stackoverflow.com/questions/54407784/laravel-dusk-how-to-change-config-values-before-each-test-for-the-browser#comment103224655_54407784
*
* #param array $variables
*/
protected function overrideDuskEnv($variables = []) {
$path = self::DOT_ENV;
if (file_exists($path)) {
$contentToPrepend = '';
foreach ($variables as $key => $value) {// Convert all new parameters to expected format
$contentToPrepend .= $key . '="' . $value . '"' . PHP_EOL;
}
$originalFileContents = $this->envContents;
$comment = '# ==============================================' . PHP_EOL . '# VARIABLES ABOVE THIS LINE are from "' . __FUNCTION__ . '" function in DuskTestCase ( https://laracasts.com/discuss/channels/testing/how-to-change-env-variable-config-in-dusk-test )' . PHP_EOL;
file_put_contents($path, $contentToPrepend . $comment . $originalFileContents); //"If they are appended, it doesn't seem to take priority."
} else {
throw new \Exception('Could not find env file to override!');
}
}
I was able to call it like this: $this->overrideDuskEnv(['QUEUE_DRIVER' => 'sync']);
But in more recent Laravel versions, environment variables are immutable (see "Read-Only env Helper").
How can I achieve my goal, where Dusk uses .env.dusk.local for most tests but then for certain tests may differ slightly?
Finally after struggling with this problem for 10+ hours, I have a solution.
/**
* #param array $variables
*/
protected function overrideDuskEnv($variables = []) {
$path = self::DOT_ENV;
if (file_exists($path)) {
$contentToAppend = '';
foreach ($variables as $key => $value) {// Convert all new parameters to expected format
$contentToAppend .= $key . '="' . $value . '"' . PHP_EOL;
}
$originalFileContents = $this->envContents;
$comment = '# ==============================================' . PHP_EOL . '# VARIABLES BELOW THIS LINE are from "' . __FUNCTION__ . '" function in DuskTestCase ( https://laracasts.com/discuss/channels/testing/how-to-change-env-variable-config-in-dusk-test )' . PHP_EOL;
$this->baseCommand->consoleOutput('Appending to ' . $path . ': ' . $contentToAppend);
file_put_contents($path, $originalFileContents . $comment . $contentToAppend); //It used to be the case that "If they are appended [rather than prepended], it doesn't seem to take priority", but after the DotEnv upgrade in Laravel 5.8, it seems prepending doesn't work and appending does.
} else {
throw new \Exception('Could not find env file to override!');
}
}
Then in my setUp() function in my Dusk test class, I call:
$this->overrideDuskEnv([
'SIGNUP_FORM_POST_PATH' => \App\Helpers\Routes::SIGNUP,
'QUEUE_DRIVER' => \App\Helpers\ConfigConstants::DUSK_CONNECTION
]);
Then in each test function after the closing of $this->browse(function (Browser $browser)... and before assertions, I call:
config(['queue.default' => \App\Helpers\ConfigConstants::DUSK_CONNECTION]); //this does not affect the headless browser but IS probably necessary here so that assertQueued knows to pull from the queue that the headless browser was using.
The tricky part to understand with Dusk is that the environment variables (and therefore the config arrays) of the console process running the tests differ from those that get used by the headless browser (simulating what a real user would experience).
By the way, I had been so hopeful about approaches like this one, but they turned out to be complete wastes of time because DuskCommand is already calling overload to make the env variables mutable.
See here for a documented approach to overriding config([]) during a dusk test:
https://gist.github.com/wrabit/e01df16858505c395b7b0d271112a023
You can also use a seperate env for all the dusk tests.
It is also mentioned in the laravel docs here https://laravel.com/docs/8.x/dusk#environment-handling
This is a bit easy but if you put any wrong entry then it will burst.
public function store(Request $request) {
foreach ($request->all() as $key => $value) {
$_ENV[$key] = $value;
}
$x = '';
unset($_ENV['_token']);
foreach ($_ENV as $key => $value) {
$x .= $key . "=" . $value . "\n";
}
base_path('.env');
file_put_contents(base_path('.env'), $x);
return redirect()->back();
}
Using
<form class="grid gap-2" action="{{ route('admin.enviroment.store') }}" method="post">
<div>
<x-label for="GOOGLE_TAG" :value="__('GOOGLE_TAG')" />
<x-input id='GOOGLE_TAG' name="GOOGLE_TAG" :value="__($_ENV['GOOGLE_TAG'])" class="w-full rounded-md dark:bg-gray-700" type="text" required />
</div>
#csrf
<x-button class="ml-auto dark:bg-blue-900/90">
{{ __('Update GOOGLE TAG') }}
</x-button>
</form>
With Laravel Blade, is there an elegant way to check if a view exists before doing an #include?
For example I'm currently doing this:
#if(View::exists('some-view'))
#include('some-view')
#endif
Which gets quite cumbersome when 'some-view' is a long string with variables inside.
Ideally I'm looking for something like this:
#includeifexists('some-view')
Or to make #include just output an empty string if the view doesn't exist.
As an aside, I would also like to provide a set of views and the first one that exists is used, e.g.:
#includefirstthatexists(['first-view', 'second-view', 'third-view'])
And if none exist an empty string is output.
How would I go about doing this? Would I need to extend BladeCompiler or is there another way?
Had a similar issue. Turns out that from Laravel 5.3 there is an #includeIf blade directive for this purpose.
Simply do #includeIf('some-view')
I created this blade directive for including the first view that exists, and returns nothing if none of the views exist:
Blade::directive('includeFirstIfExists', function ($expression) {
// Strip parentheses
if (Str::startsWith($expression, '(')) {
$expression = substr($expression, 1, -1);
}
// Generate the string of code to render in the blade
$code = "<?php ";
$code .= "foreach ( {$expression} as \$view ) { ";
$code .= "if ( view()->exists(\$view) ) { ";
$code .= "echo view()->make(\$view, \Illuminate\Support\Arr::except(get_defined_vars(), array('__data', '__path')))->render(); ";
$code .= "break; } } ";
$code .= "echo ''; ";
$code .= "?>";
return $code;
});
How can i display uploaded file name or link after successfully uploaded file at magento custom module form.I had attached screenshot for clear understanding.Please help
In order to do this, you need a custom renderer for the file input in your form.
For this create the following class:
<?php
class {{Namespace}}_{{Module}}_Block_Adminhtml_{{Entity}}_Helper_File extends Varien_Data_Form_Element_Abstract{
public function __construct($data){
parent::__construct($data);
$this->setType('file');
}
public function getElementHtml(){
$html = '';
$this->addClass('input-file');
$html.= parent::getElementHtml();
if ($this->getValue()) {
$url = $this->_getUrl();
if( !preg_match("/^http\:\/\/|https\:\/\//", $url) ) {
$url = Mage::getBaseUrl('media').'{{entity}}'.'/'.'file' . $url; //replace this with the path to the file if you upload it somewhere else
}
$html .= '<br />'.$this->_getUrl().' ';
}
$html.= $this->_getDeleteCheckbox();
return $html;
}
protected function _getDeleteCheckbox(){
$html = '';
if ($this->getValue()) {
$label = Mage::helper('{{module}}')->__('Delete File');
$html .= '<span class="delete-image">';
$html .= '<input type="checkbox" name="'.parent::getName().'[delete]" value="1" class="checkbox" id="'.$this->getHtmlId().'_delete"'.($this->getDisabled() ? ' disabled="disabled"': '').'/>';
$html .= '<label for="'.$this->getHtmlId().'_delete"'.($this->getDisabled() ? ' class="disabled"' : '').'> '.$label.'</label>';
$html .= $this->_getHiddenInput();
$html .= '</span>';
}
return $html;
}
protected function _getHiddenInput(){
return '<input type="hidden" name="'.parent::getName().'[value]" value="'.$this->getValue().'" />';
}
protected function _getUrl(){
return $this->getValue();
}
public function getName(){
return $this->getData('name');
}
}
Then you need to tell your for to use this for the file inputs. So in your edit form tab, add this right after defining the fiedlset:
$fieldset->addType('file', Mage::getConfig()->getBlockClassName('{{module}}/adminhtml_{{entity}}_helper_file'));
Replace {{Namespace}}, {{Module}} and {{Entity}} with the appropriate values keeping the case.
Namespace is the namespace of your module (D'uh), Module is the name of your module (D'uh again), and Entity is what you are managing. Can be Article, News, Files....
[EDIT]
You can build your module using this module creator. It takes care of these kind of issues.
Note: I hope this is not considered self promotion. The extension is free and I get no financial benefits out of it.
Ok I found answer myself...
just create one file and extended it with magento core abstract class that is Namespace_ModuleName_Block_Adminhtml_Modulename_Helper_File extends Varien_Data_Form_Element_Abstract
and added below code to admin form
$fieldset = $form->addFieldset('Module_form', array('legend'=>Mage::helper('Module')->__('Video information')));
$fieldset->addType('file', Mage::getConfig()->getBlockClassName('Module/adminhtml_Module_helper_file')); /* line added */
So there are instances where there could be one or more errors to report to the user (and to notify me about) that could be caused at a controller level (input, validation) or model level.
I'm considering creating a basic helper for 'feedback' that basically has global message arrays (notice, error, success)
Then either at the model or controller level, if something goes wrong (or right!), I can call the feedback function.
feedback('error','Connection is temporarily down blah')
I won't need to pass it through to my views as it will be globally set so I can just call something like $this->feedback->display_all().
Is this an ok/MVC friendly way to do things? It seems like a straight-foward method for me to implement
For my project, I created a tiny mdl_error model.
This model has one public function, throwError, and some private helpers that will show flash notices to the user and send an email to me with current values and session data if need be. The model is autoloaded and is only called if needed.
Here is basically what it looks like:
<?php
class mdl_error extends CI_Model
{
//types: error, alert, good
function throwError($type, $message, $info="", $flash=true, $email=true)
{
if($flash){
$alert = $type."|".$message;
$this->session->set_userdata(array("flash" => $alert));
}
if($email){
$problems = $this->recursivePrintingOfVariables($info);
$sessionData = $this->recursivePrintingOfVariables($this->session->userdata);
$emailMessage = "Name<br/> <br/>Something has happened. <br/> <br/>";
$emailMessage .= "The type was: {$type}<br/>The message was: {$message}<br/> <br/> <br/>";
$emailMessage .= "Here is the local variables at the time:<br/> <br/>{$problems}<br/> <br/> <br/>";
$emailMessage .= "Here is the session data:<br/> <br/>{$sessionData}<br/> <br/> <br/>";
$emailMessage .= "Please solve this problem or we are all dooooooomed.<br/><br/>Love,<br/>Website";
$this->load->library('email');
$this->email->from("my email");
$this->email->to("error#whatever.com");
$this->email->subject($type.' Message from Website');
$this->email->message($emailMessage);
$this->email->send();
}
}
function recursivePrintingOfVariables($info)
{
$keys = array_keys($info);
$string = "";
foreach($keys as $key){
$string .= $key." => ";
if(is_array($info[$key])){
$string .= "Inner Array<br/>";
$string .= "<div style='margin-left:15px;'>";
$string .= $this->recursivePrintingOfVariables($info[$key]);
$string .= "</div>";
}else{
$string .= $info[$key];
}
$string .= "<br/><br/>";
}
return $string;
}
}
Then, if there is a spot in my code where an error happens, I just call:
$this->mdl_error->throwError("error","something happend", get_defined_vars());
If you use the show_error('Your error message'); function in your controller you will achieve the same. If you want to customise the look of the error you need to work with the error_general.php file found in applications/errors.
If you also want to log the errors you could use the log_message('level', 'message'); function in your controller.