Integrate Twig with CodeIgniter 4 - codeigniter

I used Twig with Symfony and I really loved it. I now have a CodeIgniter project and I want to integrate Twig with it.
I installed the latest versions of CodeIgniter and Twig via Composer and and followed this tutorial but I believe the code in the tutorial is for CI v3.
Could anyone who has integrated Twig with CI v4 help me with the proper code please.
UPDATE
solution below!

Try this I hope it will help you
Install Composer and run the following command to get the latest version:
composer require "twig/twig:^3.0"
Then after installation add this line of code to the baseController initController method just after the parent::initController, just like the code below
namespace App\Controllers;
use CodeIgniter\Controller;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Psr\Log\LoggerInterface;
class BaseController extends Controller
{
protected $helpers = [];
protected $twig;
// protected $helper = [];
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger)
{
parent::initController($request, $response, $logger);
$appPaths = new \Config\Paths();
$appViewPaths = $appPaths->viewDirectory;
$loader = new \Twig\Loader\FilesystemLoader($appViewPaths);
$this->twig = new \Twig\Environment($loader, [
'cache' => WRITEPATH.'/cache/twig',
]);
}
}
So with this now you can call the view files in other controllers extends to parent controller BaseController
e.g
namespace App\Controllers;
class Home extends BaseController
{
public function index ()
{
// To load a template from a Twig environment, call the load() method which returns a \Twig\TemplateWrapper instance:
$template = $this->twig->load('index.html');
// To render the template with some variables, call the render() method:
return $template->render(['the' => 'variables', 'go' => 'here']);
// The display() method is a shortcut to output the rendered template.
// OR You can also load and render the template in one fell swoop:
return $this->twig->render('index.html', ['the' => 'variables', 'go' => 'here']);
// If a template defines blocks, they can be rendered individually via the renderBlock() call:
return $template->renderBlock('block_name', ['the' => 'variables', 'go' => 'here']);
// Note any of them above will work
}
}
If you still want to use view() with twig like codeigniter 4 default view function you can modify the Common.php file in app directory
by adding this block of code below.
if (!function_exists('view'))
{
function view($tpl, $data = []) {
$appPaths = new \Config\Paths();
$appViewPaths = $appPaths->viewDirectory;
$loader = new \Twig\Loader\FilesystemLoader($appViewPaths);
$twig = new \Twig\Environment($loader, [
'cache' => WRITEPATH.'/cache/twig',
]);
if (!stripos($tpl, '.twig')) {
$tpl = $tpl . '.twig';
}
return $twig->render($tpl, $data);
}
}
Then in controller call it like this
return view('index', ['name' => 'Chibueze Agwu'])
Then in view file index.twig
<!DOCTYPE html>
<html>
<head>
<title>My Webpage</title>
</head>
<body>
<h1>My Webpage</h1>
{{ name }}
</body>
</html>
This will output
My Webpage
Chibueze Agwu
I haven't test this code but I hope it will work. If not call my attentions.
In order to obey the the rule of DRY (DO NOT REPEAT YOURSELF), you can go ahead to improve the code I will do that later

I found the solution some time ago and I'm posting it in case some people stumble across the question.
First of all, all your controllers must extend BaseController; this controller is available by default when you install CodeIgniter 4.
Create a custom helper file and put in [project-name]/appstarter/app/Helpers.
IMPORTANT
the name of your helper must be [name]_helper.php or it will not work!
for example mine is called custom_helper.php
Create the following function in the custom helper you just created:
use Twig\Environment;
use Twig\Extension\DebugExtension;
use Twig\Loader\FilesystemLoader;
use Twig\TwigFilter;
if (!function_exists('twig_conf')) {
function twig_conf() {
// the follwing line of code is the only one needed to make Twig work
// the lines of code that follow are optional
$loader = new FilesystemLoader('Views', '../app/');
// to be able to use the 'dump' function in twig files
$twig = new Environment($loader, ['debug' => true]);
$twig->addExtension(new DebugExtension());
// twig lets you create custom filters
$filter = new TwigFilter('_base_url', function ($asset) {
return base_url() . '/' . $asset;
});
$twig->addFilter($filter);
return $twig;
}
}
NOTE
before creating any custom filter, make sure Twig doesn't already has one built-in.
Now in the BaseController you'll find an empty array called $helpers. You must put the name of your custom helper in it. Mine is called custom_helper.php; so the code looks like this for me:
protected $helpers = ['custom'];
Just below the array you'll find the constructor for BaseController and this is where the Twig library will be initialized; by calling the function you created in your custom helper:
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger) {
parent::initController($request, $response, $logger);
$this->twig = twig_conf();
}
Now you are good to go! To render your twig files in any controller:
return $this->twig->render('twig_name', $dataArray);

Try this I hope it will help you.
Install Composer and run the following command to get the latest version:
composer require "twig/twig:^3.0"

Related

Laravel Blade how to pass $attributes to component from controller

I have a blade component containing $attributes; It would be a bag of attributes when the component called from another blade template but when from the controller via view() the $attributes is undefined! How can I pass data as $attributes from the controller?
Component: sample
<div {{ $attributes->except('content') }}>{{ $content }}</div>
Template: works well.
...
<x-sample class="test" content="test"/>
...
Controller: Error Undefined variable $attributes
$attributes = ['class' => 'test', 'content' => 'test'];
view('components.sample', $attributes)->render();
view('components.sample', ['attributes' => $attributes])->render();
view('components.sample')->with($attributes)->render();
UPDATE (Solution):
It works:
view('components.sample', [
'prop1' => '...',
'prop2' => '...',
'attributes' => new ComponentAttributeBag(['attr1' => '...', 'attr2' => '...']),
])->render();
what is happening is components inherit variable from view, if you define your variable from home view for example, it should work in component from that view. Then when you include the view to about page the variables wont be recognized since they are not inherited from about view. And in Laravel you cannot pass data directly form controller to component. but laravel have solved it by View Composer.
Since I dont like too much complication especially with providers I edited my AppServiceProvider you can create your own provider.
in YourServiceProvider in my case AppServiceProvider in boot() function there are three options you can use one. I recommend there 3rd one since its clean
public function boot()
{
// option1 - Every single view
View::share('shops', Shop::orderBy('name')->get());
// option2 - Gradular views with wildcards
View::composer(['client.*'], function ($view){
$view->with('shops', Shop::orderBy('name')->get());
});
// option3 - Dedicated class
View::composer(['client.includes.*','client.create-product','client.cart'], ShopsComposer::class);
View::composer(['client.includes.*','client.cart'], CartComposer::class);
}
If you use the 3rd method you have to create ViewComposer class
<?php
namespace App\Http\View\Composers;
use App\Models\CartItem;
use Illuminate\Support\Facades\Session;
use Illuminate\View\View;
class ShopsComposer
{
public function compose(View $view){
$shop = auth()->user()->shops->sortBy('name');
$cartItem = new CartItem();
$cartCount = 0;
if (Session::has('cartId')) {
$carts = Session::get('cartId');
$cartItems = $cartItem->with('products')->where('cart_id', '=', $carts->id)->get();
foreach ($cartItems as $item) {
$cartCount += $item->quantity;
}
}
$view->with('shopsComposer', $shop)->with('cartCount', $cartCount);
}
}
The variables you define there will be available to all the views. that includes components. Since component inherit variable from view file.
I am sorry I had to share my working example, since I am running out of time. I hope it works if I understood well your question.

Set $pageName globally in pagination Laravel

We can use a custom $customPageName in laravel's paginate() function's 3rd parameter.By that we can use site.com/url?$customPageName=n . Is there any way we can set the custom page name globally ? In AppServiceProvider or somewhere like that?So we don't need to define every time?
There are many possible solution. I give you two that IMO are the best:
1: Create a trait and use it in every component
I love to use traits, they avoid a lot of redundancy and can be easily invoked everywhere:
trait PaginationTrait
{
const PAGE_NAME = 'your_global_value'
public function paginate($query, $perPage, $columns = [''], $page_name = null)
{
return $query->paginate($perPage, $columns, $page_name ?: self::PAGE_NAME);
}
}
This solution requires anyway that you pass as input a Builder instance, and may cause a logic problem. So let's move to the next solution
2: Config files
Simply set a variable in an existing config file or in a new config file:
// Example: app.php
return [
// [...]
'page_name' => 'your_custom_value' // or env('APP_PAGE_NAME', 'your_custom_value'),
// [...]
];
And in your controller you can retrive the value as follows:
public function index() {
// [...]
$result = MyModel::paginate($per_page, $columns, config('app.page_name'));
}
3: App\Http\Controllers\Controller
The third solution is the easiest. Set a constant in your App\Http\Controllers\Controller class (as I wrote for the trait part) and by the OOP rules, it will be inherited to all your controllers:
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
const PAGE_NAME = 'your_global_value';
}
And in your controller:
public function index() {
// [...]
$result = MyModel::paginate($per_page, $columns, self::PAGE_NAME);
}
In my opinion, if you simply have to set this global variable, solution 2 and 3 are the best... If you have to create a custom pagination logic, then I think that creating a specific Trait or Class is a good choice

Multiple routes to same Laravel resource controller action

I like to use resource controllers in Laravel, as it makes me think when it comes to data modelling. Up to now I’ve got by, but I’m now working on a website that has a public front-end and a protected back-end (administration area).
I’ve created a route group which adds an “admin” prefix, like so:
Route::group(array('before' => 'auth', 'prefix' => 'admin'), function()
{
Route::resource('article', 'ArticleController');
Route::resource('event', 'EventController');
Route::resource('user', 'UserController');
});
And I can access the methods using the default URL structure, i.e. http://example.com/admin/article/1/edit.
However, I wish to use a different URL structure on the front-end, that doesn’t fit into what resource controllers expect.
For example, to access an article, I’d like to use a URL like: http://example.com/news/2014/06/17/some-article-slug. If this article has an ID of 1, it should (under the hood) go to /article/1/show.
How can I achieve this in Laravel? In there some sort of pre-processing I can do on routes to match dates and slugs to an article ID, and then pass that as a parameter to my resource controller’s show() method?
Re-visiting this, I solved it by using route–model binding and a pattern:
$year = '[12][0-9]{3}';
$month = '0[1-9]|1[012]';
$day = '0[1-9]|[12][0-9]|3[01]';
$slug = '[a-z0-9\-]+';
// Pattern to match date and slug, including spaces
$date_slug = sprintf('(%04d)\/(%02d)\/(%02d)\/(%s)', $year, $month, $day, $slug);
Route::pattern('article_slug', $date_slug);
// Perform the route–model binding
Route::bind('article_slug', function ($slug) {
return Article::findByDateAndSlug($date_slug);
});
// The actual route
Route::get('news/{article_slug}', 'ArticleController#show');
This then injects an Article model instance into my controller action as desired.
One simple solution would be to create one more route for your requirement and do the processing there to link it to the main route. So, for example:
//routes.php
Route::get('/arical/{date}/indentifier/{slug}', array (
'uses' => 'ArticleController#findArticle'
));
//ArticleContoller
public function findArticle($date,$slug){
$article = Article::where('slug','=','something')->first(); //maybe some more processing;
$article_id = $article->id;
/*
Redirect to a new route or load the view accordingly
*/
}
Hope this is useful.
It seems like if Laravel 4 supports (:all) in routing, you would be able to do it with ease, but unfortunately (:all) is not supported in Laravel 4.
However, Laravel 4 allows detecting routes by regular expression, so we can use ->where('slug', '.*').
routes.php: (bottom of the file)
Route::get('{slug}', 'ArticleController#showBySlug')->where('slug', '.*');
Since Laravel will try to match the top most route in routes.php first, we can safely put our wildcard route at the bottom of routes.php so that it is checked only after all other criteria are already evaluated.
ArticleController.php:
class ArticleController extends BaseController
{
public function showBySlug($slug)
{
// Slug lookup. I'm assuming the slug is an attribute in the model.
$article_id = Article::where('slug', '=', $slug)->pluck('id');
// This is the last route, throw standard 404 if slug is not found.
if (!$article_id) {
App::abort(404);
}
// Call the controller's show() method with the found id.
return $this->show($article_id);
}
public function show($id)
{
// Your resource controller's show() code goes here.
}
}
The code above assumes that you store the whole URI as the slug. Of course, you can always tailor showBySlug() to support a more advanced slug checking.
Extra:
You could also do:
Route::get('{category}/{year}/{slug}', 'ArticleController#showBySlug')->where('slug', '.*');
And your showBySlug() would just have additional parameters:
public function showBySlug($category, $year, $slug)
{
// code
}
Obviously you can extend to month and day, or other adaptations.

Laravel 4 : Route to localhost/controller/action

I'm more or less new to Laravel 4. I've never used routes before but normally what I'm used to is url/controller/action and then the backend routing for me. I've read the documentation for routes and controllers a few times as well as read through some tutorials and so, I'm trying to figure out how to get this to work without writing a route for every controller and action.
I tried something like
Route::get('{controller}/{action}', function($controller, $action = 'index'){
return $controller."#".$action;
});
Now then, I know this is wrong since it doesn't work, but what am I missing? On most tutorials and stuff I'm seeing an route for more or less every controller and action like:
Route::get('/controller/action' , 'ControllerName#Action');
Which seems silly and like a waste of time to me.
Is there anyway to achieve what I want?
If you are looking for a more automated routing, this would be the Laravel 4 way:
Route:
Route::controller('users', 'UsersController');
Controller (in this case UsersController.php):
public function getIndex()
{
// routed from GET request to /users
}
public function getProfile()
{
// routed from GET request to /users/profile
}
public function postProfile()
{
// routed from POST request to /users/profile
}
public function getPosts($id)
{
// routed from GET request to: /users/posts/42
}
As The Shift Exchange mentioned, there are some benefits to doing it the verbose way. In addition to the excellent article he linked, you can create a name for each route, for example:
Route::get("users", array(
"as"=>"dashboard",
"uses"=>"UsersController#getIndex"
));
Then when creating urls in your application, use a helper to generate a link to a named route:
$url = URL::route('dashboard');
Links are then future proofed from changes to controllers/actions.
You can also generate links directly to actions which would still work with automatic routing.
$url = URL::action('UsersController#getIndex');
app\
controllers\
Admin\
AdminController.php
IndexController.php
Route::get('/admin/{controller?}/{action?}', function($controller='Index', $action='index'){
$controller = ucfirst($controller);
$action = $action . 'Action';
return App::make("Admin\\{$controller}Controller")->$action();
});
Route::get('/{controller?}/{action?}', function($controller='Index', $action='index'){
$controller = ucfirst($controller);
$action = $action . 'Action';
return App::make("{$controller}Controller")->$action();
});
I come from .Net world and routing is typically done:
/{Controller}/{action}/{id}
Which looks like:
/Products/Show/1 OR /Products/Show/Beverages
In Laravel I accomplish this routing like so:
Route::get('/{controller?}/{action?}/{id?}', function ($controller='Home', $action='index', $id = null) {
$controller = ucfirst($controller);
return APP::make("{$controller}Controller")->$action($id);
});
The controller would look roughly like so:
class ProductsController extends BaseController {
public function Show($id) {
$products = array( 1 => array("Price" => "$600","Item" => "iPhone 6"),
2 => array("Price" => "$700", "Item" => "iPhone 6 Plus") );
if ($id == null) {
echo $products[1]["Item"];
} else {
echo $products[$id]["Item"];
}
}
}

Codeigniter MVC Sample Code

Hi
I am following the getting started guide for Codeigniterr given at http://www.ibm.com/developerworks/web/library/wa-codeigniter/
I have followed the instruction to create the front view and added controller to handle form submission. Ideally, when i submit the form, it should load the model class and execute the function to put details on the database, but instead it is just printing out the code of the model in the browser.
**Code of view (Welcome.php)**
----------------
<?php
class Welcome extends Controller {
function Welcome()
{
parent::Controller();
}
function index()
{
$this->load->helper('form');
$data['title'] = "Welcome to our Site";
$data['headline'] = "Welcome!";
$data['include'] = 'home';
$this->load->vars($data);
$this->load->view('template');
}
function contactus(){
$this->load->helper('url');
$this->load->model('mcontacts','',TRUE);
$this->mcontacts->addContact();
redirect('welcome/thankyou','refresh');
}
function thankyou(){
$data['title'] = "Thank You!";
$data['headline'] = "Thanks!";
$data['include'] = 'thanks';
$this->load->vars($data);
$this->load->view('template');
}
}
/* End of file welcome.php */
/* Location: ./system/application/controllers/welcome.php */
**Code of Model**
--------------
class mcontacts extends Model{
function mcontacts(){
parent::Model();
}
}
function addContact(){
$now = date("Y-m-d H:i:s");
$data = array(
'name' => $this->input->xss_clean($this->input->post('name')),
'email' => $this->input->xss_clean($this->input->post('email')),
'notes' => $this->input->xss_clean($this->input->post('notes')),
'ipaddress' => $this->input->ip_address(),
'stamp' => $now
);
$this->db->insert('contacts', $data);
}
**OUTPUT after clicking submit**
-----------------------------
class mcontacts extends Model{ function mcontacts(){ parent::Model(); } } function addContact(){ $now = date("Y-m-d H:i:s"); $data = array( 'name' => $this->input->xss_clean($this->input->post('name')), 'email' => $this->input->xss_clean($this->input->post('email')), 'notes' => $this->input->xss_clean($this->input->post('notes')), 'ipaddress' => $this->input->ip_address(), 'stamp' => $now ); $this->db->insert('contacts', $data); }
I have tried doing these things
1. Making all PHP codes executable
2. Change ownership of files to www-data
3. make permission 777 for whole of www
But, the code of model seems to be just printed ... PLEASE HELP
Just a few minor points that might help you:
In your controller, point the index method at the method you would like to call on that page. For example:
function index()
{
$this->welcome();
}
That will help keep things clean and clear, especially if anyone else comes in to work on the code with you later. I chose welcome because that's the name of your controller class and that will keep things simple.
In your model, this:
class mcontacts extends Model{
Should be:
class Mcontacts extends Model{
Capitalize those class names! That could be giving you the trouble you describe.
See here for more info on this: http://codeigniter.com/user_guide/general/models.html
Don't use camel case in your class or method names. This isn't something that will cause your code to fail, but it's generally accepted practice. See Codeigniter's PHP Style guide for more information on this: http://codeigniter.com/user_guide/general/styleguide.html
It's difficult to see with the formatting as it is, but do have an extra curly brace after the constructor method (mcontacts()) in the model? This would cause problems! Also although the code looks generally ok, there are probably better ways to use the framework especially if you do anything more complicated than what you've shown. For example, autoloading, form validation etc. Can I suggest you have a read of the userguide? It's very thorough and clear and should help you alot. http://codeigniter.com/user_guide/index.html

Resources