I have a project that is sharing much of the same functionality across multiple clients use, each with their own sub-domain to access and use. For each instance, I'd like to add the odd bit of additional formatting or information to some views and was hoping to check whether a file exists upon rendering the view.
For example:
Let's say I have three different instances of the product (so three subdomains) and when a user logs in they view /dashboard by default. If the view is rendered from views/dashboard.blade.php, I'd like to check whether there is a file called views/subdomain.dashboard.blade.php and if so, use that as the view instead. I'm thinking this may be possible using the View Composer but not entirely sure how as still getting up to speed with Laravel.
You can make a helper for that.
if (!function_exists('subdomain_view')) {
function subdomain_view($view = null, $data = [], $mergeData = [])
{
$subdomain = request()->route()->parameter('subdomain');
$altView = "{$subdomain}.{$view}";
return view(
view()->exists($altView) ? $altView : $view,
$data,
$mergeData
);
}
}
It gets the subdomain, prefixes it to the view name, check if the prefixed view exists. If it does it uses that view, otherwise it uses the original view. It also uses the same signature as the view() helper.
Note that it makes use of the request() and and view() helpers without parameters. The default behavior of these helpers when called without parameters is to return the an instance of Illuminate\Http\Request and Illuminate\Contracts\View\Factory, respectively.
Another way to make this work is to provide your own implementation of Illuminate\View\ViewFinderInterface. You can check the Illuminate\View\FileViewFinder class that implements this interface. You can just subclass from it and override the find() method. Then register the new class.
Update: You don't need to even subclass FileViewFinder, just tweak how FileViewFinder is registered inside the service provider. Other solutions include adding view namespaces. See here.
Related
I am working on a newly created Phalcon project, and I don't really know how to actually use multiples views.
What is the entry point? I don't really know when each method in the controller is called, under which conditions, etc.
Where is the control flow defined? is it based in the name of the view? or is there a place where you can register them?
Phalcon is a bit different than other well-known PHP frameworks, in that not much is pre-configured or pre-built by default. It's quite loosely-coupled. So you have to decide where and how your control flow will work. This means that you will need to dig deeper in the documentation and also that there could be different way to achieve the same thing.
I'm going to walk you through a simple example and provide references, so you can understand it more.
1) You would start by defining a bootstrap file (or files) that will define the routes, or entry points, and will setup and create the application. This bootstrap file could be called by an index.php file that is the default file served by the web server. Here is an example of how such bootstrap file will define the routes or entry points (note: these are just fragments and do not represent all the things that a bootstrap file should do):
use Phalcon\Di\FactoryDefault;
// initializes the dependency injector of Phalcon framework
$injector = new FactoryDefault();
// defines the routes
$injector->setShared('router', function () {
return require_once('some/path/routes.php');
});
Then it the routes.php file:
use Phalcon\Mvc\Router;
use Phalcon\Mvc\Router\Group as RouterGroup;
// instantiates the router
$router = new Router(false);
// defines routes for the 'users' controller
$user_routes = new RouterGroup(['controller' => 'users']);
$user_routes->setPrefix('/users');
$user_routes->addGet('/show/{id:[0-9]{1,9}}', ['action' => 'show']);
$router->mount($user_routes);
return $router;
Im defining routes in an alternate way, by defining routes groups. I find it to be more easy to organize routes by resource or controller.
2) When you enter the url example.com/users/show/123, the routes above will match this to the controller users and action show. This is specified by the chunks of code ['controller' => 'users'], setPrefix('/users') and '/show/{id:[0-9]{1,9}}', ['action' => 'show']
3) So now you create the controller. You create a file in, let's say, controllers/UsersController.php. And then you create its action; note the name that you used in the route (show) and the suffix of Action:
public function showAction(int $id) {
// ... do all you need to do...
// fetch data
$user = UserModel::findFirst(blah blah);
// pass data to view
$this->view->setVar('user', $user);
// Phalcon automatically calls the view; from the manual:
/*
Phalcon automatically passes the execution to the view component as soon as a particular
controller has completed its cycle. The view component will look in the views folder for
a folder named as the same name of the last controller executed and then for a file named
as the last action executed.
*/
// but in case you would need to specify a different one
$this->view->render('users', 'another_view');
}
There is much more stuff related to views; consult the manual.
Note that you will need to register such controller in the bootstrap file like (Im also including examples on how to register other things):
use Phalcon\Loader;
// registers namespaces and other classes
$loader = new Loader();
$loader->registerNamespaces([
'MyNameSpace\Controllers' => 'path/controllers/',
'MyNameSpace\Models' => 'path/models/',
'MyNameSpace\Views' => 'path/views/'
]);
$loader->register();
4) You will also need to register a few things for the views. In the bootstrap file
use Phalcon\Mvc\View;
$injector->setShared('view', function () {
$view = new View();
$view->setViewsDir('path/views/');
return $view;
});
And this, together with other things you will need to do, particularly in the bootstrap process, will get you started in sending requests to the controller and action/view defined in the routes.
Those were basic examples. There is much more that you will need to learn, because I only gave you a few pieces to get you started. So here are some links that can explain more. Remember, there are several different ways to achieve the same thing in Phalcon.
Bootstrapping:
https://docs.phalconphp.com/en/3.2/di
https://docs.phalconphp.com/en/3.2/loader
https://docs.phalconphp.com/en/3.2/dispatcher
Routing: https://docs.phalconphp.com/en/3.2/routing
Controllers: https://docs.phalconphp.com/en/3.2/controllers
More on Views (from registering to passing data to them, to templating and more): https://docs.phalconphp.com/en/3.2/views
And a simple tutorial to teach you some basic things: https://docs.phalconphp.com/en/3.2/tutorial-rest
The application begins with the routing stage. From there you grab the controller and action from the router, and feed it to the dispatcher. You set the view then call the execute the dispatcher so it access your controller's action. From there you create a new response object and set its contents equal to the view requests, and finally send the response to the client's browser -- both the content and the headers. It's a good idea to do this through Phalcon rather than echoing directly or using PHP's header(), so it's only done at the moment you call $response->send(); This is best practice because it allows you to create tests, such as in phpunit, so you can test for the existence of headers, or content, while moving off to the next response and header without actually sending anything so you can test stuff. Same idea with exit; in code, is best to avoid so you can write tests and move on to the next test without your tests aborting on the first test due to the existence of exit.
As far as how the Phalcon application works, and in what steps, it's much easier to follow the flow by looking at manual bootstrapping:
https://docs.phalconphp.com/en/3.2/application#manual-bootstrapping
At the heart of Phalcon is the DI, the Dependency Injection container. This allows you to create services, and store them on the DI so services can access each other. You can create your own services and store them under your own name on the DI, there's nothing special about the names used. However depending on the areas of Phalcon you used, certain services on the DI are assumed like "db" for interacting with your database. Note services can be set as either shared or not shared on the DI. Shared means it implements singleton and keeps the object alive for all calls afterwards. If you use getShared, it does a similar thing even if it wasn't initially a shared service. The getShared method is considered bad practice and the Phalcon team is talking about removing the method in future Phalcon versions. Please rely on setShared instead.
Regarding multiple views, you can start with $this->view->disable(); from within the controller. This allows you to disable a view so you don't get any content generated to begin with from within a controller so you can follow how views work from within controllers.
Phalcon assumes every controller has a matching view under /someController/someView followed by whatever extension you registered on the view, which defaults to .volt but can also be set to use .phtml or .php.
These two correspond to:
Phalcon\Mvc\View\Engine\Php and Phalcon\Mvc\View\Engine\Volt
Note that you DON'T specify the extension when looking for a template to render, Phalcon adds this for you
Phalcon also uses a root view template index.volt, if it exists, for all interactions with the view so you can use things like the same doctype for all responses, making your life easier.
Phalcon also offers you partials, so from within a view you can render a partial like breadcrumbs, or a header or footer which you'd otherwise be copy-pasting into each template. This allows you to manage all pages from the same template so you're not repeating yourself.
As far as which view class you use within Phalcon, there's two main choices:
Phalcon\Mvc\View and Phalcon\Mvc\View\Simple
While similar, Phalcon\Mvc\View gives you a multiple level hierarchy as described before with a main template, and a controller-action based template as well as some other fancy features. As far as Phalcon\Mvc\View\Simple, it's much more lightweight and is a single level.
You should be familiar with hierarchical rendering:
https://docs.phalconphp.com/en/3.2/views#hierarchical-rendering
The idea is with Phalcon\Mvc\View that you have a Main Layout (if this template exists) usually stored in /views/index.volt, which is used on every page so you can toss in your doctypes, the title (which you would set with a variable the view passed in), etc. You'd have a Controller Layout, which would be stored under /views/layouts.myController.volt and used for every action within a controller (if this template exists), finally you'd have the Action Layout which is used for the specific action of the controller in /views/myController/myAction.volt.
There are all types of ways you can break from Phalcon's default behavior. You can do the earlier stated $this->view->disable(); so you can do everything manually yourself so Phalcon doesn't assume anything about the view template. You can also use ->pick to pick which template to use if it's going to be different than the controller and action it's ran in.
You can also return a response object from within a controller and Phalcon will not try to render the templates and use the response object instead.
For example you might want to do:
return $this->response->redirect('index/index');
This would redirect the user's browser to said page. You could also do a forward instead which would be used internally within Phalcon to access a different controller and/or action.
You can config the directory the views are stored with setViewsDir. You can also do this from within the controller itself, or even within the view as late as you want, if you have some exceptions due to a goofy directory structure.
You can do things like use $this->view->setTemplateBefore('common') or $this->view->setTemplateAfter('common'); so you can have intermediate templates.
At the heart of the view hierarchy is <?php echo $this->getContent(); ?> or {{ content() }} if you're using Volt. Even if you're using Volt, it gets parsed by Phalcon and generates the PHP version with $this->getContent(), storing it in your /cache/ directory, before it is executed.
The idea with "template before" is that it's optional if you need another layer of hierarchy between your main template and your controller template. Same idea with "template after" etc. I would advise against using template before and after as they are confusing and partials are better suited for the task.
It all depends on how you want to organize your application structure.
Note you can also swap between your main template to another main template if you need to swap anything major. You could also just toss in an "if" statement into your main template to decide what to do based on some condition, etc.
With all that said, you should be able to read the documentation and make better sense of how to utilize it:
https://docs.phalconphp.com/en/3.2/api/Phalcon_Mvc_View
I created Phalcon PHP app,
I have 3 different user profiles: (ID: 1) Administrators, (ID: 2) Accountants and (ID: 3) Warehouses.
I want my app able to render views based on those profiles, for example
controllerName/index.1.volt //for Administrators
controllerName/index.2.volt //for Accountants
controllerName/index.3.volt //for Warehouses
but when those files weren't found, my app will fallback to:
controllerName/index.volt
How do I accomplish that?
One approach, although messy, would be to use controlerName/index.volt as the "landing page" then from there check an if-statement to decide what the user's role is. Then from the if-statement use a partial like {{ partial("index.1.volt") }} but you'd need to hard-code this for every controller... yuck...
A good solution which I'd recommend, though, would be to make use of the View's exists method to check if the view exists from within your controller. The idea is you pass this method the string path to the view file you're looking for but omitting the extension. The reason you omit the extension is because Phalcon allows you to use multiple rendering engines so an application using a mixture of .volt and .phtml would work.
Assuming you were using user roles something like this:
define('GUEST_ROLE',0);
define('ADMIN_ROLE',1);
define('ACCOUNTANT_ROLE',2);
define('WAREHOUSE_ROLE',3);
(with the guest role with a value of 0) I would suggest having all your controllers extend a ControllerBase then define the following two methods in your ControllerBase:
public function getUserLevel()
{
if($this->session->has('userLevel'))
{
$userLevel=$this->session->get('userLevel');
return (int)$userLevel;
}else{
return 0;//default to guest
}
}
protected function initialize()
{
$controllerName=$this->dispatcher->getControllerName();
$actionName=$this->dispatcher->getActionName();
$userLevel=$this->getUserLevel();
if($this->view->exists("$controllerName/$actionName.$userLevel"))
{
$this->view->pick("$controllerName/$actionName.$userLevel");
}
//No reason to add an else, Phalcon defaults to "$controllerName/$actionName"
}
Just make sure, that if you ever need to define a custom "initialize" method for a specific controller which extends the ControllerBase, e.g. to add a title prefix to all pages related to the controller, that you call parent::initialize(); otherwise it won't get called. But that's only if you're going to be overriding the method.
Chances are you're already using a ControllerBase and doing similar logic already, if so, you'd need to edit your already existing "initialize" method and merge my code with yours.
Happy coding.
I know that Blade already caches the compiled PHP for all blade views, but I would like to take this a step further. A website that I'm working on is modularized into component views and then pieced together in the default controller. Each of the "widgets" has its own view, which rarely changes content (with the exception of a few frequently updating ones). So, I'd like to cache the HTML output of these rarely-changing views to prevent them from being evaluated on every page load.
In Laravel 3 we could do something like so (credit Laravel forums):
Event::listen(View::loader, function($bundle, $view)
{
return Cache::get($bundle.'::'.$view, View::file($bundle, $view,
Bundle::path($bundle).'view'));
});
Unfortunately, View::loader has entirely disappeared in Laravel 4. When digging through \Illuminate\View\View and \Illuminate\View\Environment, I discovered that each view dispatches an event named "composing: {view_name}". Listening for this event provides the view name and data being passed to it on each view render, however returning from the callback does not have the same effect as it did in Laravel 3:
Event::listen('composing: *', function($view) {
if(!in_array($view->getName(), Config::get('view.alwaysFresh'))) {
// Hacky way of removing data that we didn't pass in
// that have nasty cyclic references (like __env, app, and errors)
$passedData = array_diff_key($view->getData(), $view->getEnvironment()
->getShared());
return Cache::forever($view->getName() . json_encode($passedData), function() {
return 'test view data -- this should appear in the browser';
});
}, 99);
The above does not circumvent the normal view including and rendering process.
So how can you circumvent normal view rendering and return cached content from this composing event? Is it possible currently in Laravel without some ugly hackery?
Quick and Dirty
Well, one option, as I'm sure you know, is to cache the items inside of controllers as the View is being rendered. I suspect you don't want to do that, as it's less maintainable in the long-run.
More maintainable(?) method
However, if the View loader/renderer doesn't fire an event where you want, you can create one. Because every package/library in Laravel 4 is set in the App container, you can actually replace the View library with your own.
The steps I would take is:
Create a library/package. The goal is to create a class which extends Laravel's view logic. After taking a look, you might want to extend this one - This is the View facade
If you extended the View facade with your own (aka if my assumption on the file in step 1 is correct), you'll then just need to replace the alias for View in app/config/app.php with your own.
Edit- I played with this a bit. Although I don't necessarily agree with caching a View result, vs caching sql queries or the "heavier lifts", here is how I'd go about doing this in Laravel 4:
The View rendering in Laravel 4 doesn't fire an event that let's us cache the result of a view. Here's how I've added in that functionality to cache a view's result.
You may want to consider the ramifications of caching a view's result. For instance, this doesn't get around doing the hard work of talking to a datbase to get the data needed for the view. In any case, this gives a good overview on extending or replacing core items.
First, create a package and set up its autoloading. I'll use the namespace Fideloper\View. It's autoloading in composer.json will looks like this:
"autoload": {
"classmap": [
"app/commands",
"app/controllers",
"app/models",
"app/database/migrations",
"app/database/seeds",
"app/tests/TestCase.php"
],
"psr-0": {
"Fideloper": "app/"
}
},
Next, create a class to replace the View facade. In our case, that means we'll be extending Illuminate\View\Environment.
In this class, we'll take the result of the View being rendered and add some logic to cache (or not cache) it. Here is Fideloper/View/Environment.php:
<?php namespace Fideloper\View;
use Illuminate\View\Environment as BaseEnvironment;
use Illuminate\View\View;
class Environment extends BaseEnvironment {
/**
* Get a evaluated view contents for the given view.
*
* #param string $view
* #param array $data
* #param array $mergeData
* #return \Illuminate\View\View
*/
public function make($view, $data = array(), $mergeData = array())
{
$path = $this->finder->find($view);
$data = array_merge($mergeData, $this->parseData($data));
$newView = new View($this, $this->getEngineFromPath($path), $view, $path, $data);
// Cache Logic Here
return $newView;
}
}
So, that's where the bulk of your work will be - filling out that // Cache Logic Here. However, we have some plumbing left to do.
Next, we need to set up our new Environment class to work as a Facade. I have a blog post about creating Laravel facades. Here's how to accomplish that in this case:
Create the facade for our new Environment. We'll name it fideloper.view in code.
<?php namespace Fideloper\View;
use Illuminate\Support\Facades\Facade;
class ViewFacade extends Facade {
/**
* Get the registered name of the component.
*
* #return string
*/
protected static function getFacadeAccessor() { return 'fideloper.view'; }
}
Then, create the Service Provider which will tell Laravel what to create when fideloper.view is called. Note that this needs to mimic functionality of the Illuminate\View\ViewServiceProvider for creating the extended Environment class.
<?php namespace Fideloper\View;
use Illuminate\Support\ServiceProvider;
class ViewServiceProvider extends ServiceProvider {
public function register()
{
$this->app['fideloper.view'] = $this->app->share(function($app)
{
// Next we need to grab the engine resolver instance that will be used by the
// environment. The resolver will be used by an environment to get each of
// the various engine implementations such as plain PHP or Blade engine.
$resolver = $app['view.engine.resolver'];
$finder = $app['view.finder'];
$env = new Environment($resolver, $finder, $app['events']);
// We will also set the container instance on this view environment since the
// view composers may be classes registered in the container, which allows
// for great testable, flexible composers for the application developer.
$env->setContainer($app);
$env->share('app', $app);
return $env;
});
}
}
Lastly, we need to hook this all together and tell Laravel to load our Service Provider and replace Illuminate's View facade with our own. Edit app/config/app.php:
Add the Service Provider:
'providers' => array(
// Other providers
'Fideloper\View\ViewServiceProvider',
),
Replace the View facade with our own:
'aliases' => array(
// Other Aliases
//'View' => 'Illuminate\Support\Facades\View',
'View' => 'Fideloper\View\ViewFacade',
),
You'll then be able to use whatever logic you wish in the View::make() method!
Finally
It's worth noting that there are some patterns to load in multiple "requests" per web request. Symfony, for instance, let's you define controllers as servers. Zend has (had?) a concept of Action Stacks, which let you
... effectively help you create a queue of [controller] actions to execute during the request.
Perhaps you'd like to explore that possibility within Laravel, and cache the results of those "actions" (vs caching a view directly).
Just a thought, not a recommendation.
There is a library for caching views/parts in Laravel(and not only) - Flatten.
It is a powerful cache system for caching pages at runtime. What it does is quite simple : you tell him which page are to be cached, when the cache is to be flushed, and from there Flatten handles it all. It will quietly flatten your pages to plain HTML and store them. That whay if an user visit a page that has already been flattened, all the PHP is highjacked to instead display a simple HTML page. This will provide an essential boost to your application's speed, as your page's cache only gets refreshed when a change is made to the data it displays.
To cache all authorized pages in your application via the artisan flatten:build command. It will crawl your application and go from page to page, caching all the pages you allowed him to.
Flushing
Sometimes you may want to flush a specific page or pattern. If per example you cache your users's profiles, you may want to flush those when the user edit its informations. You can do so via the following methods :
// Manual flushing
Flatten::flushAll();
Flatten::flushPattern('users/.+');
Flatten::flushUrl('http://localhost/users/taylorotwell');
// Flushing via an UrlGenerator
Flatten::flushRoute('user', 'taylorotwell');
Flatten::flushAction('UsersController#user', 'taylorotwell');
// Flushing template sections (see below)
Flatten::flushSection('articles');
link to - https://github.com/Anahkiasen/flatten
I want to use codeigniter for an ecommerce project I'm working on but I think I need some custom routing and I'm not sure if this is possible. I want to be able to use this url:
http://myecommsite.com/store/mens
By default in CI this would call the mens function in the store class. What I actually want is it to call a generic function in the store class and feed in 'mens' as a paremeter. The reason for this is that this site needs to have a mens,womens and childrens section.
Is this possible?
Also When I get further down the line...i.e
http://myecommsite.com/store/mens/category1/category2
how do I make Ci work with this?
Simply define a custom route in application/config/routes.php
Something like, for your url http://myecommsite.com/store/mens
$route['store/(:any)'] = "store/customfunction/$1";
This way all requests will be mapped to your "customfunction" method, which takes the parameter "mens"
You might also want to consdere the __remap() function, which overrides the methods (as opposed to the routing, which overrides the whole URI) Quoting from the manual:
If your controller contains a function named __remap(), it will always
get called regardless of what your URI contains. It overrides the
normal behavior in which the URI determines which function is called,
allowing you to define your own function routing rules.
So you can use a __remap() function in your controller store, and anything will be redirected to that. Any segments after the method name are passed into __remap() as second parameter, and you can use this array with call_user_func_array().
This could come in handy for your second examples of URI. Might be something like
function __remap('mymethod',$array = array())
{
return call_user_func_array('mymethod',$array);
}
and in your method "mymethod" you pick the array element and do what you need to do
I have a site that has a lot of pages that lye at the root (ex. /contact, /about, /home, /faq, /privacy, /tos, etc.). My question is should these all be separate controllers or one controller with many methods (ex. contact, about, index within a main.php controller )?
UPDATE:
I just realized that methods that are within the default controller don't show in the url without the default controller (ie. main/contact wont automatically route to /contact if main is the default controller ). So you would need to go into routes and override each page.
If all of these are just pages, I would recommend putting them into a single controller. I usually end up putting static pages like this into a 'pages' controller and putting in routes for each static page to bypass the '/pages' in my URLs.
If they are share the same functionality, so they should be in the same controller.
for example, if all of them are using the same model to take content from, so, one controller can easily handle it.
Why in one controller? because you always want to reuse your code.
class someController{
function cotact(){
print $this->getContentFromModel(1);
}
function about(){
print $this->getContentFromModel(2);
}
function home(){
print $this->getContentFromModel(3);
}
private function getContentFromModel($id){
return $this->someContentModel->getContentById($id);
}
}
(instead of print, you should use load a view)
See in my example how all of the function are using the same getContentFromModel function to share the same functionality.
but this is one case only, there could be ther cases that my example can be bad for...
in application/config/routes.php
$route['contact'] = "mainController/contact";
$route['about'] = "mainController/about";
$route['home'] = "mainController/home";
$route['faq'] = "mainController/faq";
$route['privacy'] = "mainController/privacy";
and you should add all of these methods within the mainController.php
You can also save the content of the pages in your database, and them query it. For instance, you can send the url as the keyword to identify the page content
$route['contact'] = "mainController/getContent/contact";
$route['about'] = "mainController/getContent/about";
$route['home'] = "mainController/getContent/home";
$route['faq'] = "mainController/getContent/faq";
$route['privacy'] = "mainController/getContent/privacy";
in this case you only have to create one method named "getContent" in the controller "mainController" and this method will look something like this:
class mainController extends CI_Controller
{
public function getContent($param)
{
$query = $this->db->get_where('mytable', array('pageName' => $param));
// then get the result and print it in a view
}
}
Hope this works for you
The page names you listed should probably be different methods inside your main controller. When you have other functionality that is related to another specific entity, like user, you can create another controller for the user entity and have different methods to display the user, update the user, register the user. But its all really a tool for you to organize your application in a way that makes sense for your domain and your domain model.
I've written a blog post about organizing CodeIgniter controller methods that might be helpful to you. Check it out here: http://caseyflynn.com/2011/10/26/codeigniter-php-framework-how-to-organize-controllers-to-achieve-dry-principles/