How can i see all view file names which was rendered for current page ?
in debug console there is no any info about which view files were loaded during page generation.
There is no native solution for this but it can be accomplished in a couple ways.
I think the easiest is to override the CViewRenderer class and keep a list of files that renderFile is called with. Overriding the class is a matter of adding
'viewRenderer'=>array
(
'class'=>'MyViewRenderer',
),
In the components part in your config.
It could look like this in its simplest form:
class MyViewRenderer extends CViewRenderer
{
public function renderFile($context, $sourceFile, $data, $return)
{
echo "Rendering " . $sourceFile . PHP_EOL;
return parent::renderFile($context, $sourceFile, $data, $return)
}
}
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 can't seem to get Silverstripe 4 to display images included in SiteConfig in my templates at all.I used to be able to just doe something like $SiteConfig.Logo and it would print out a automatic tag.
CustomSiteConfig:
<?php
use SilverStripe\Forms\FieldList;
use SilverStripe\ORM\DataExtension;
use SilverStripe\Forms\TextField;
use SilverStripe\Forms\TextareaField;
use SilverStripe\Forms\HeaderField;
use SilverStripe\AssetAdmin\Forms\UploadField;
use SilverStripe\Assets\Image;
use SilverStripe\ORM\DataObject;
use SilverStripe\CMS\Model\SiteTree;
class CustomSiteConfig extends DataExtension
{
private static $db = [
];
private static $has_one = [
'Logo' => Image::class,
'MobileLogo' => Image::class
];
private static $owns = [
'Logo',
"MobileLogo"
];
public function updateCMSFields(FieldList $fields)
{
$uploader = UploadField::create('Logo');
$uploader->setFolderName('Logo');
$uploader->getValidator()->setAllowedExtensions(['png','gif','jpeg','jpg']);
$fields->addFieldsToTab('Root.Main', [
HeaderField::create('hf2','Default logo'),
$uploader
]);
$uploader2 = UploadField::create('MobileLogo');
$uploader2->setFolderName('MobileLogo');
$uploader2->getValidator()->setAllowedExtensions(['png','gif','jpeg','jpg']);
$fields->addFieldsToTab('Root.Main', [
HeaderField::create('hf3','Mobile Logo'),
$uploader2
]);
}
}
But when I try in my template file. I get no URL
$SiteConfig.Logo
or
$SiteConfig.Logo().Link
etc
Nothing works?
A few things to check:
Verify that $SiteConfig is available as variable at that point in your template (Try using $SiteConfig.Title)
Verify that the extension is actually added to SiteConfig (do you see the CMS Fields?)
Did you add $owns later? run ?flush=1 again and re-save the SiteConfig *
Verify that both the SiteConfig and the File is published. (Save & Publish the SiteConfig twice, then check in the file manager if the file is published) **
[*] $owns is just a directive that when SiteConfig->doPublish() is called, it will also publish all files
[**] I've seen a bug that DataObjects don't actually publish files sometimes. Saving twice might work.
Just like Zauberfisch said, your image is probably not published. However, publishing the image after writing the owner can be tricky.
I usually through in this code
public function onAfterWrite()
{
parent::onAfterWrite();
if ( $this->LogoID ) {
$this->Logo()->doPublish();
}
if ( $this->MobileLogoID ) {
$this-> MobileLogo()->doPublish();
}
}
It's messy, I know, but it can save you a couple of hours. After saving you can remove it as the $owns hook will start to kick-in to all newly created objects.
We can Use This one
$SiteConfig.Logo.URL
I'm new to PHP Laravel framework. I'm studying it and playing with simple examples of code. My problem is that my views do not output anything - a blank white screen appears when I try to reach controller methods, for example, localhost/my-application/cms/action1
My routes file:
Route::controller('cms', 'CmsController');
My controller:
class CmsController extends BaseController {
public function getIndex()
{
View::make('cms.index');
}
public function getAction1()
{
View::make('cms.action1');
}
public function getAction2()
{
View::make('cms.action2');
}
}
My views are located in views/cms. They are very simple, for example:
<h1>Action1</h1>
<?php echo 'this is Action1'; ?>
And these views do not output anything, just simple blank white page appears. I tried to:
1) rename views files, and Laravel threw exception - "view not found", or so.
2) move view::make() methods to Routes file - the views were displayed then.
So where is the problem?
The bootstrap index.php file in laravel is inside the public folder.
So unless you've created a vhost for your application, you have to access it like
localhost/my-application/public/cms/action1
EDIT
Forget it. The problem is that you do not return the view::make from each function.
return View::make('cms.index');
Open cart is based on CodeIgniter as I understand but in CodeIgniter to load and use the model you do something like this
$this->load->model('Model_name');
$this->Model_name->function();
In OpenCart you do something like this
$this->load->model('catalog/product');
$this->model_catalog_product->getTotalProducts()
How does this work and where does the "model_catalog_product" come from?
It seems like they have 0 developer documentation besides their forums.
OpenCart's loader class seems to be inspired by CodeIgniter, but it's not based on it. You can look into the source of OpenCart, see file system/engine/loader.php (Line 39).
public function model($model) {
$file = DIR_APPLICATION . 'model/' . $model . '.php';
$class = 'Model' . preg_replace('/[^a-zA-Z0-9]/', '', $model);
if (file_exists($file)) {
include_once($file);
// Right here. Replaces slash by underscore.
$this->registry->set('model_' . str_replace('/', '_', $model), new $class($this->registry));
} else {
trigger_error('Error: Could not load model ' . $model . '!');
exit();
}
}
You can clearly see that it replaces slashes with underscores and append 'model_' before the model's name. That's why you end up with model_catalog_product.
The model_catalog_product comes from the path folder structure and file name within the model folder, so model_catalog_product is the model/catalog/product.php file, with the extension removed and the slashes changed to underscores. Also, notice that the model class name also refers to a similar structure, which is ModelCatalogProduct. As for the documentation, there was some documentation for developers, but just checked briefly and it appears that it's been removed for whatever reason. I learn't from lots of trial and error unfortunately, as have most developers using it
I've never worked with Zend Framework before, but I've worked with others (CodeIgniter, Kohana, etc). Right now I was asked to just show a view that wasn't existing so I started looking into the documentation and examples I could find and I've always found examples that use the Model part of the MVC, but in this case I just need to load a view and I can't figure out how to do that. I have this:
File: "BookController":
require_once("Initiate.php");
class BookController extends Initiate {
public function init() {
parent::init();
}
public function bookAction(){
#$client = Zend_Auth::getInstance()->getIdentity();
$view = new Zend_View();
echo $this->view->render('book.phtml');
#$this->view->assign("book", $client);
#echo $this->view->render('book.phtml');
}
the view is called "book.phtml" and is found in /application/views/scripts/bookapi/
What am I missing?
Given that eveything is setup correctly with MVC, your Controller should extend Zend_Controller_Action:
class BookController extends Zend_Controller_Action
{
public function indexAction()
{
$this->view->funnyText = 'This is a funny text.';
}
}
Then, in your application/views/scripts/book/ folder, there has to be a index.phtml. Which could look like that:
<p>
<?php echo $this->funnyText; ?>
</p>
That's it, nothing more required.
Btw. doesn't make sense to have a controller called book and then also an action called book
you don't have to manage view yourself there is an Action Controller Helper called View Renderer it does your job for you all you need to follow is its naming convention i.e if your controller name is 'BookController' then its view file should be located at views/scripts/book/boo.phtml .
Assuming BookController extends Zend_Controller, it should automatically setup the view and you shouldn't have to render it. Your view file should be in /application/views/scripts/book/book.phtml. Follow the quick start for more information.