I just asked a question ( Templates In Kohana 3.1 ) about templates and now I know that I should use Kostache. It's a module for the Mustache template language.
Anyway, I just enabled Kostache module for my Kohana 3.1 and all works. It's installed correctly! What to do next? How to use it?
Where should I put my views now?
What my controller should extend?
How to assign variable?
How to make header, footer etc. for views?
Maybe there are step to step guide for it? This and this won't help me a lot...
Where should I put my views now?
View classes contain logic for your templates and by convention should be stored in classes/view/{template name}.php
Templates contain your HTML and should be stored in the templates directory in the root of your module, e.g. templates/login.mustache
By default kostache will try and work out the location of the template based on your view class' name.
If your view class is called View_Admin_Login then kostache will look for templates/admin/login.mustache
What my controller should extend?
You do not need to extend any special controllers, the normal Controller will work fine as a base.
How to assign variable
Controller:
$view = new View_Admin_Login;
$view->message = 'Hello';
$this->response->body($view->render());
Template:
{{message}}
Of course, any methods or variables you declare in your view class will also be available in
the template. If there is a class variable and method with the same name then the method will always take precedence over the variable.
How to make header, footer etc. for views
It will help if you read the kostache guide. The idea is that your views extend Kostache_Layout, see also the layout template
There's lots of demos and examples in both of the repositories that you said won't help you.
Try this...
//application/classes/controller:
class Controller_Test extends Controller {
public function action_index()
{
$view = new View_Home;
$this->response->body($view->render());
}
}
//application/classes/view/Home.php:
class View_Home {
public $name = "Chris";
public $value = 10000;
public function taxed_value() {
return $this->value - ($this->value * 0.4);
}
public $in_ca = true;
protected $_layout = 'home';
}
//application/templates/home.mustache:
Hello {{name}}
You have just won ${{value}}!
{{#in_ca}}
Well, ${{ taxed_value }}, after taxes.
{{/in_ca}}
In your APPPATH/classes/controller/Test.php:
class Controller_Test extends Controller{
public function action_index()
{
$renderer = Kostache::factory();
$this->response->body($renderer->render(new View_Test));
}
}
In your MODPATH/KOstache/classes/view/Test.php:
class View_Test
{
public $name = "Chris";
public $value = 10000;
public function taxed_value() {
return $this->value - ($this->value * 0.4);
}
public $in_ca = true;
}
In your MODPATH/KOstache/classes/templates/test.mustache:
Hello {{name}}
You have just won ${{value}}!
{{#in_ca}}
Well, ${{ taxed_value }}, after taxes.
{{/in_ca}}
In the following example, do not pay attention to naming classes and inheritance:
More examples on GitHub
Related
The problem
The default namespace for view components is App\View\Components with the folder being app/View/Components. I am setting up a DDD file structure and wish to do two things:
Move "shared" view components to a namespace and folder of App\ViewComponents and src/app/ViewComponents respectively
Have view components specific to individual "apps" with their own namespace and folder of App\MyApplication\ViewComponents and /src/app/MyApplication/ViewComponets respectively.
The new App namespace/folder setup is done via composer psr-4 autoload keys and works fine. But Laravel always used the App\View\Components namespace when trying to load components.
My attempt
I have solved the first part of my problem, but I am hoping that there is a better way. For instance when I want to move views, I can just set the view.paths config directive in my AppServiceProvider but I don't see a similar way of, essentially, adding namespaces to where Laravel looks for view components. So what I ended up doing was:
Create a ViewServiceProvider class, extending Illuminate\View\ViewServiceProvider::class and point to it in bootstrap/app.php instead
In there, override the registerBladeEngine method, in there pointing towards my own BladeCompiler class instead of the built-in one
public function registerBladeEngine($resolver)
{
// The Compiler engine requires an instance of the CompilerInterface, which in
// this case will be the Blade compiler, so we'll first create the compiler
// instance to pass into the engine so it can compile the views properly.
$this->app->singleton('blade.compiler', function () {
return new BladeCompiler(
$this->app['files'],
$this->app['config']['view.compiled'],
);
});
$resolver->register('blade', function () {
return new CompilerEngine(
$this->app['blade.compiler']
);
});
}
In my own BladeCompiler class, which extends Illuminate\View\Compilers\BladeCompiler, override the component() and compileComponentTags() methods - basically anywhere that referenced View\\Components - with pretty much a carbon copy but instead using ViewComponents and also made sure that where they return a Illuminate\View\Compilers\ComponentTagCompiler I instead referenced my own ComponentTagCompiler
In my own TagCompiler I override the guessClassName() method, again with essentially a carbon copy, just renaming View\\Components to ViewComponents
As you can see, that's quite a lot of work just to change the path. And I also want to add another path. Multiple "apps" run under the same Laravel codebase, so for instance we might have App\Website\, App\Admin and App\Blog and, depending on which app is currently running, load a different namespace for the running app, i.e. the blog would be App\Blog\ViewComponents pointing to src/app/Blog/ViewComponents.
Is there a way to achieve this without as much overriding as above? If not, can you suggest a way to achieve the second part of the requirement?
Note: I haven't ruled out using sub folders and continuing with everything under the main App\View\Components namespace just yet - I don't want to fight Laravel more than I have to and am willing to concede if there's no better way, but if I can achieve the folder structure I want it would feel a lot tidier.
Update; got a working implementation by using a configuration and php 8 annotations
Follow the steps below to make it possible to add more lookup folders for the blade view components feature, based on your question and details you have provided. It would have helped to have posted that code you already had. But I have added a possible solution to get it to work, using Annotations and using a config with a namespace/path map.
Depending on how you switch between one application and the other, from which the details are not provided in your question, you have to modify the way the configuration is retreived in the MyComponentTagCompiler class.
Blade compiler
In order to change the ComponentTagCompiler we need to change the BladeCompiler class:
namespace App;
class YourBladeCompiler extends \Illuminate\View\Compilers\BladeCompiler
{
protected function compileComponentTags($value)
{
if (! $this->compilesComponentTags) {
return $value;
}
return (new \App\MyComponentTagCompiler( //it is about this line
$this->classComponentAliases, $this->classComponentNamespaces, $this
))->compile($value);
}
}
Service provider
Now register the YourBladeCompiler in YourViewServiceProvider :
class YourViewServiceProvider extends \Illuminate\View\ViewServiceProvider
{
public function registerBladeEngine($resolver)
{
$this->app->singleton('blade.compiler', function () {
return new \App\YourBladeCompiler( //it is about this line
$this->app['files'],
$this->app['config']['view.compiled'],
);
});
$resolver->register('blade', function () {
return new CompilerEngine(
$this->app['blade.compiler']
);
});
}
}
MyComponentTagCompiler
This is an implemention I created that works with PHP 8 Attributes, given below:
namespace App;
#[\Attribute]
class ViewComponentName
{
public string $name;
public string $package;
public function __construct(string $name, string $package)
{
$this->name = $name;
$this->package = $package;
}
}
With this attribute, you can declare the package name and component name on the view component class (see example at the bottom). So during lookup the component can be matched on these parameters.
But you can change it to your own requirements if needed.
What it does:
It first let's Laravel lookup the View Component through it's own mechanisms, in the parent::componentClass method.
If no component is found and an exception (InvalidArgumentException) is thrown, after which my implementation will walk through the given paths and namespaces (from the getLookupPaths method) and see if an attribute matches the component name and package name. If so it returns this class and the view component is loaded accordingly.
namespace App;
use App\View\ViewComponentName;
use Illuminate\View\Compilers\ComponentTagCompiler;
class MyComponentTagCompiler extends ComponentTagCompiler
{
protected function getLookupPaths() : array
{
/*
* add some logic here to get an application specific configuration
* since you have multiple application in one, I cannot know it works in your
* application, since the details are not provided in the question
*/
return config('view_component_paths');
}
private function getFiles(string $dir) : array
{
return scandir($dir);
}
private function isPhpFile(string $file) : bool
{
return strpos($file, ".php");
}
private function getClassNamespace(string $file, string $folderNamespace) : string
{
$class = str_replace(".php", "", $file);
$classNamespace = $folderNamespace . "\\" . $class;
return $classNamespace;
}
private function getComponentName(string $file, string $namespace) : ?ViewComponentName
{
$classNamespace = $this->getClassNamespace($file, $namespace);
$reflection = new \ReflectionClass($classNamespace);
if(method_exists($reflection, 'getAttributes')) {
$attribute = $reflection->getAttributes()[0];
if ($attribute->getName() == ViewComponentName::class) {
return $attribute->newInstance();
}
}
return null;
}
public function componentClass(string $component)
{
try {
parent::componentClass($component);
} catch(\InvalidArgumentException $e) {
list($lookupComponentPackage, $lookupComponentName) = explode("-", $component);
foreach($this->getLookupPaths() as $namespace=>$dir) {
foreach ($this->getFiles($dir) as $file) {
if ($this->isPhpFile($file)) {
if($componentName = $this->getComponentName($file, $namespace)) {
if($componentName->name == $lookupComponentName && $componentName->package == $lookupComponentPackage) {
return $this->getClassNamespace($file, $namespace);
}
}
}
}
}
throw $e;
}
}
}
Where the config contains (config/view_component_paths.php):
return [
"App\\Test"=>__DIR__ . "/Test/"
];
If you wish to replace the default laravel behavior completely or do not like my implementation based on annotations, consider implementing your own version of the method:
public function componentClass(string $component)
{
//return the class name here based the component name
//without calling parent
dd($component);
}
Example view component
namespace App\Test;
use App\View\ViewComponentName;
use Illuminate\View\Component;
#[ViewComponentName('test', 'namespace')]
class MyViewComponent extends Component
{
public function render()
{
return view('components.test');
}
}
In blade:
<x-namespace-test />
It should now be working. I think this is enough information to give you an idea of how to implement this in your own application. There seems to be no other way than to extend some base classes. But looking at this answer, it is possible to create a high level implementation based on a global lookup configuration and php annotations (or some other mechanism you wish, for example converting the class name with namespace to a view component name).
Old answer
Problem 2 as defined in your question
Have view components specific to individual "apps" with their own namespace and folder of App\MyApplication\ViewComponents and /src/app/MyApplication/ViewComponets respectively.
Sadly there seems to be no way of defining multiple class paths for view components in Laravel. But you can however change the application path and namespace prefix. As far as I found out you only have to overwrite the following properties in the Application class.
bootstrap/app.php
Replace the following lines:
$app = new Illuminate\Foundation\Application(
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);
With:
class YourApplication extends \Illuminate\Foundation\Application
{
protected $namespace = "App\\MyApplication";
protected $appPath = __DIR__ . "/../app/MyApplication";
}
$app = new YourApplication(
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);
This is enough to change the app folder to another and gives you an idea of how to change it dynamically for having multiple apps in different namespaces. If you now run laravel commands like php artisan make:component Test1234 it is created in your new app folder: app/MyApplication/View/Components/Test1234.php.
Hardcoded paths
Some paths like View/Components are hardcoded in Laravel, and therefor not that easy to change. If you change as defined above, in this case the view components namespace becomes: App\MyApplication\View\Components and the path: app/MyApplication/View/Components.
Problem 1 as defined in your question
Move "shared" view components to a namespace and folder of App\ViewComponents and src/app/ViewComponents respectively
When you change application paths as explained above it is not possible to have a "shared" View Component folder. Laravel, as it seems, has only one default View Components path, which is based on hard coded paths and a dynamic namespace prefix as explained above. But you can of course, create a shared namespace and register the view components manually:
View component (app/ViewComponents/ folder)
namespace App\ViewComponents;
use Illuminate\View\Component;
class Test extends Component
{
public function render()
{
return view('components.test');
}
}
Don't forget the components.test blade view.
ServiceProvider
\Blade::component("shared-test",\App\ViewComponents\Test::class);
Blade
<x-shared-test />
I have a backend controller implements Backend\Behaviors\RelationController, I just want to set default values for the related model depending on the parent model values.
I have tried the following: model.beforeCreate, formExtendFields but no luck.
Thank you all.
After whole day of searching, I found the solution, it is documented no where on OctoberCMS website, I inspected the source file Backend\Behaviors\RelationController, after that I came with the below solution.
You should implement relationExtendViewWidget on your controller, then you can access the model from: $widget->model, something like below:
class Members extends Controller
{
public $implement = [
'Backend\Behaviors\RelationController',
];
public function relationExtendViewWidget($widget, $field)
{
$member = Member::findOrFail($this->params[0]);
$widget->model->course_id = $member->course_id;
$widget->model->member_id = $member->id;
}
public function relationExtendManageWidget($widget, $field)
{
$member = Member::findOrFail($this->params[0]);
$widget->model->course_id = $member->course_id;
$widget->model->member_id = $member->id;
}
}
I believe this should be documented somewhere on OctoberCMS documentation
In Magento $this->__('Create an Account') How this echo Create an Account?
abstract class Mage_Core_Helper_Abstract{ public function __()
{
$args = func_get_args();
$expr = new Mage_Core_Model_Translate_Expr(array_shift($args), $this->_getModuleName());
array_unshift($args, $expr);
return Mage::app()->getTranslator()->translate($args);
}
I saw that __ function in Mage_Core_Helper_Abstract Class.but i cant understand Mage::app()->getTranslator()->translate($args) what's happending in that getTranslator function.
public function getTranslator()
{
if (!$this->_translator) {
$this->_translator = Mage::getSingleton('core/translate');
}
return $this->_translator;
}
Mage::getSingleton('core/translate') what's happening there ? and why in this function call like core/translate which file its denote and how it Create an Account text?
You might search how the magento translator works
What ever text is written in $this->_('') will dynamically translated to the current locale which is loaded in your current store(That text must be specified in magento-root/app/locale//.csv)
I think the below answer might be helpfull
How does Magento translate works?
I have been reading an article for many times and yet, I can't still understand some parts.
Link for the article : Model-View-Confusion part 1: Why the model is accessed by the view in MVC
The code below is the one I think I am confused on.
class ListView extends View {
public $model;
public $template;
public $listTemplate;
public $errorTemplate;
public $itemName = 'items';
public function output() {
$result = $this->model->findAll();
if (count($result) > 0) {
$this->template = $this->getTemplate($this->listTemplate);
$this->template->addSet($this->itemName, $result);
} else {
$this->template = $this->getTemplate($this->errorTemplate);
}
return $this->template->render();
}
}
And the controller looks like this :
class UserController extends Controller {
public $viewName = 'ListView';
public function showList() {
$this->view->model = $this->model->user;
$this->view->listTemplate = 'UserList.tpl';
$this->view->errorTemplate = 'ErrorNoUsers.tpl';
}
}
As I can understand the template was assigned to a result of a method inherited from the View named getTemplate passed with a method from the View again named listTemplate
like this $this->getTemplate($this->listTemplate)
What I am confused on is that the $template suddenly had a method, which means it becomes a class . right here $this->template->addSet($this->itemName, $result); and `$this->template->render();
Do you have any idea what happened right there?
Firstly, full disclosure: I'm the author of that article.
It wasn't intended to be a complete code solution but an example of the benefits of applying a full MVC separation of concerns instead of the PAC pattern which is claimed to be MVC by most of the PHP community. How it works behind the scenes is beyond the scope of the article. However, what it was supposed to be demonstrating is that the View should encapsulate the template. In that example the template object could be a Smarty Template, a Twig instance or anything.
In hindsight I shouldn't have named the variables which reference file names "template". The confusion you have is understandable: $this->listTemplate; is a reference to the file containing the template code, and $this->template; is an instance of a template object (could be smarty, twig, anything else) that loads the file referenced in listTemplate.
I'll have a look at amending it to be clearer. I wrote that two years ago and there's several things I'd word differently and explain slightly better in the examples if I wrote it again.
I have two controllers a and b.
I would like to call a method of controller a from a method of controller b.
Could anyone help explain how I can achieve this?
This is not supported behavior of the MVC System. If you want to execute an action of another controller you just redirect the user to the page you want (i.e. the controller function that consumes the url).
If you want common functionality, you should build a library to be used in the two different controllers.
I can only assume you want to build up your site a bit modular. (I.e. re-use the output of one controller method in other controller methods.) There's some plugins / extensions for CI that help you build like that. However, the simplest way is to use a library to build up common "controls" (i.e. load the model, render the view into a string). Then you can return that string and pass it along to the other controller's view.
You can load into a string by adding true at the end of the view call:
$string_view = $this->load->view('someview', array('data'=>'stuff'), true);
test.php Controller File :
Class Test {
function demo() {
echo "Hello";
}
}
test1.php Controller File :
Class Test1 {
function demo2() {
require('test.php');
$test = new Test();
$test->demo();
}
}
Very simple way in codeigniter to call a method of one controller to other controller
1. Controller A
class A extends CI_Controller {
public function __construct()
{
parent::__construct();
}
function custom_a()
{
}
}
2. Controller B
class B extends CI_Controller {
public function __construct()
{
parent::__construct();
}
function custom_b()
{
require_once(APPPATH.'controllers/a.php'); //include controller
$aObj = new a(); //create object
$aObj->custom_a(); //call function
}
}
I agree that the way to do is to redirect to the new controller in usual cases.
I came across a use case where I needed to display the same page to 2 different kind of users (backend user previewing the page of a frontend user) so in my opinion what I needed was genuinely to call the frontend controller from the backend controller.
I solved the problem by making the frontend method static and wrapping it in another method.
Hope it helps!
//==========
// Frontend
//==========
function profile()
{
//Access check
//Get profile id
$id = get_user_id();
return self::_profile($id);
}
static function _profile($id)
{
$CI = &get_instance();
//Prepare page
//Load view
}
//==========
// Backend
//==========
function preview_profile($id)
{
$this->load->file('controllers/frontend.php', false);
Frontend::_profile($id);
}
I posted a somewhat similar question a while back, but regarding a model on CI.
Returning two separate query results within a model function
Although your question is not exactly the same, I believe the solution follows the same principle: if you're proposing to do what you mention in your question, there may be something wrong in the way you're coding and some refactoring could be in order.
The take home message is that what you're asking is not the way to go when working with MVC.
The best practice is to either use a Model to place reusable functions and call them in a controller that outputs the data through a view -- or even better use helpers or libraries (for functions that may be needed repeatedly).
You can do like
$result= file_get_contents(site_url('[ADDRESS TO CONTROLLER FUNCTION]'));
Replace [ADDRESS TO CONTROLLER FUNCTION] by the way we use in site_url();
You need to echo output in controller function instead of return.
You can use the redirect() function.
Like this
class ControllerA extends CI_Controller{
public function MethodA(){
redirect("ControllerB/MethodB");
}
}
Controller to be extended
require_once(PHYSICAL_BASE_URL . 'system/application/controllers/abc.php');
$report= new onlineAssessmentReport();
echo ($report->detailView());
You can use the redirect URL to controller:
Class Ctrlr1 extends CI_Controller{
public void my_fct1(){
redirect('Ctrlr2 /my_fct2', 'refresh');
}
}
Class Ctrlr2 extends CI_Controller{
public void my_fct2(){
$this->load->view('view1');
}
}
very simple
in first controllr call
$this->load->model('MyController');
$this->MyController->test();
place file MyController.php to /model patch
MyController.php should be contain
class MyController extends CI_Model {
function __construct() {
parent::__construct();
}
function test()
{
echo 'OK';
}
}