I'm using Webpack with the CleanWebpackPlugin, but I use an index.php. So HtmlWebpackPlugin isn't an option. I found a different plugin called WebpackManifestPlugin. This creates a file with the files and their hash called manifest.json. It looks like this:
{
"main.js": "css.d915ef.js",
"main.css": "main.d915ef.css"
}
But how do I use this in my head?
<link rel="stylesheet" type="text/css" href="dist/main.css">
Because this doesn't work.
Use that function in php script
function asset($asset_name)
{
$manifest = file_get_contents("./manifest.json");
$manifest = json_decode($manifest, true); //decode json string to php associative array
if (!isset($manifest[$asset_name])) return $asset_name; //if manifest.json doesn't contain $asset_name then return $asset_name itself
return "/dist/" . $manifest[$asset_name];
}
It will read and parse manifest.json and replace "main.js" with "css.d915ef.js"
You can use asset() function while generating yout html, like this:
<link rel="stylesheet" type="text/css" href="<?php echo asset("main.js"); ?>">
Please, be aware, that file_get_contents("./manifest.json") will only work correctly if your php script and manifest.json is in the same folder. If not - you need to provide correct path to manifest.json here.
I have a similar situation with Create React App and a PHP file.
The Create React App configuration creates an asset-manifest.json file in which all the files that are needed in the page are listed under the entrypoints key.
"entrypoints": [
"static/js/runtime-main.bff00530.js",
"static/js/2.8bab6741.chunk.js",
"static/css/main.262e5e5f.chunk.css",
"static/js/main.e2f4dbf8.chunk.js"
]
In my code I'm doing the following (expanding on #krylov123's answer):
// get the original values
$manifest = file_get_contents("./asset-manifest.json");
$manifest_values = json_decode($manifest, true);
$entrypoints = $manifest_values['entrypoints'];
Then I'm creating two separate arrays: one for the css and another for the js files. In order to do so I've created an helper function filterPaths (you may create your own):
// return only paths continaint a specific string
function filterPaths($paths, $value): array {
return array_filter($paths, function ($filename) use ($value): bool {
return strpos($filename, $value) !== false;
});
}
Here I create the two arrays:
// filter '.css' files
$css_files = filterPaths($entrypoints, '/css/');
// filter '.js' files
$js_files = filterPaths($entrypoints, '/js/');
Then in the html part I loop over the two arrays in order to print link tags for CSS and script tags for JavaScript.
This is the syntax using blade, but you can adapt it to normal php:
#foreach($css_files as $link)
<link href="{{ $link }}" rel="stylesheet">
#endforeach
#foreach($js_files as $link)
<script src="{{ $link }}"></script>
#endforeach
Another option is to disable hash inserting via webpack.config.js file.
You need to find all [hash] occurence and remove it from webpack.config.js. Then use npm run to recompile everything.
For example, you have
output: {
path: path.resolve(__dirname, './public/'),
filename: '[name].[hash].js'
}
Replace it with:
output: {
path: path.resolve(__dirname, './public/'),
filename: '[name].js'
}
Related
My set-up comprises a lib folder with classes and a view folder with PHP files, that produce output. The views are imported inside a View class similar to this:
class View {
public function render(string $basename, Array $params) : string {
extract($params, EXTR_PREFIX_INVALID, 'v');
ob_start();
include sprintf('%s/views/%s.php', dirname(__DIR__), $basename);
$out = ob_get_contents();
ob_end_clean();
return $out;
}
}
I have basically two problems with Psalm in this situation:
For View::render it reports a UnresolvableInclude. I can even type the $basename with something like
#param "view1"|"view2"|"about" $basename
without effect. The unresolvable include remains.
The extract() puts the content of $params in the local scope, where the view files are included. This allows me to have
<?=escape($foo)?>
“tags” in my view files with $params === ['foo' => 'bar']. However, Psalm doesn’t catch up on this and reports a lot of UndefinedGlobalVariable problems.
My question: How can I tell psalm about the view files and the variables? Or alternatively, how can I re-structure this code so that psalm can test it for me?
There's a demo TemlateChecker plugin in Psalm's repo that seems to do something similar: it looks at the docblock in the view file for the tag like #variablesfrom ClassName::method and makes them available in the template file. Or just properties on $this variable from that method, not sure. It's also mentioned in Psalm docs: Checking non-PHP files.
Alternatively, you could wrap your template into a minimal method/function as technically view is just a function that takes a bunch of variables and returns a string: https://psalm.dev/r/66898ee87f
<?php class HomePageView { // view starts here
/** #param list<string> $sections */
public function render(
string $title,
array $sections
): string { ob_start();
?>
<html>
<head>
<title><?=$title?></title>
</head>
<body>
<?php foreach ($sections as $section): ?>
<section><?=$section?></section>
<?php endforeach; ?>
</body>
</html>
<?php return ob_get_contents(); }} // view ends here ?>
This way any tool that analyzes code (including Psalm, but not limited to) would be able to understand it.
Trying to retrieve an image from outside public. I have been using file controller helper but still can't get it to work:
here is my code that is returned as AJAX to update the IMG SRC path
$imgPATH = $this->getParameter('kernel.project_dir').'/uploads/user_profile_pictures/';
$ext = [".png", ".jpg", ".jpeg", ".gif"];
foreach ($ext as $x) {
$imgPath = $this->getParameter('kernel.project_dir').'/uploads/user_profile_pictures/'.$usertoPic.$x;
if (file_exists($imgPath)) {
return $this->file($imgPath , 'userProfilePicture.png' , ResponseHeaderBag::DISPOSITION_INLINE);
}
}
The image is found by PHP but I get the following broken HTML in the browser; I think this is because it is binary ? how can I convert it to HTML compliant ?
<img id="userIMG" src="�PNG��IHDR���S���S���lЬW���pHYs��.#��.#x�v��OiCCPPhotoshop ICC profile��xڝSgTS�=���BK���KoR RB���&*!J�!��Q�EEȠ�����Q,������������{�kּ������>�����H3Q5��B�������.#�$p��d!s�#��~<<+"���x���M��0���B�\���t�8K��#z�B��#F���&S���`�cb��P-�`" �������{�[�!���="" e�d�h;���v�e�x0�fk�9��-�0iwfh��������="" �0q��)�{�`�##x����f�w<�+��*��x��<�$9e�[-qww.(�i+6aa�#.�y�2�4�����������x����6��_-��"bb���ϫp#���t~��,="" ��;�m��%�h^�u��f�#�����w�p�~<<e���������j�b[a�w}�g�_�w�l�~<�����$�2]�g�����l�ϒ="" �b��g�����"�ib�x*�qq�d���2�"�b�)�%��d��,�="">
Your browser cannot have access to a file that is not in the public (for Symfony 4) folder. That is pretty much the whole point of having a "public" directory.
What you can do is serving the file directly as a binary response given a certain link in your app, like documented here:
https://symfony.com/blog/new-in-symfony-3-2-file-controller-helper
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class ImgController extends Controller
{
/**
* #Route("/get-my-img")
*/
public function getImg()
{
$basePath = $this->getParameter('kernel.root_dir').'/uploads/user_profile_pictures/';
return $this->file($basePath . "img.png");
}
}
// this should work quite simple:
// site/public/css/style.css
<link href="{{ asset('css/style.css') }}" rel="stylesheet" />
// site/upload/favicon.ico
<img src="{{ asset('../upload/favicon.ico') }}" />
$target_dir = '/upload/favicon.ico'; // param in config
$file = $this->getParameter('kernel.project_dir') . $target_dir;
if(is_file($file)) dump('found: ' . $file);
I have a directory structure like this:
www/
index.php
my-library/
my-library.php
assets/
my-library.css
images/
loading.gif
I need my-library.php to inject stylesheets into index.php. To do so, I need to get the relative path from index.php to my-library/ -- which in this particular case, would simply be "my-library".
From within my-library.php, is it possible for me to acquire this relative path?
Or must index.php supply it, with something like the following?
<?php
require "my-library/my-library.php";
$mlib->linkroot='my-library';
?>
To clarify, below I have included a more detailed representation of what I'm trying to do:
index.php:
<?php require "my-library/my-library.php"; ?>
<!doctype html>
<head>
<title>Testing My Library</title>
<?php $mlib->injectAssets(); ?>
</head>
<body>..</body>
my-library.php:
<?php
class MyLibrary(){
public $rootpath;
public $linkroot;
function __construct(){
$this->rootpath= __DIR__; // absolute path to my library's root directory (for serverside use)
$this->linkroot = "???"; // relative path to my library's root from index.php (for clientside use, like linking in stylesheets)
}
function injectAssets(){
$csslink = $this->linkroot.'/assets/my-library.css';
echo '<link href="'.$csslink.'" rel="stylesheet" />';
}
}
$mlib = new MyLibrary();
?>
The line I'm interested in figuring out, would be $this->linkroot = "???";.
I'm practically trying to acquire the string that was used to include/require the current script.
I got it! I only had to build a Rube Goldberg Machine to do it!
Thanks PHP.
$linkroot = ascertainLinkroot();
function ascertainLinkroot(){
return makeRelativePath(
getTopScriptPath(),
__DIR__
);
}
function getTopScriptPath(){
$backtrace = debug_backtrace(
defined( "DEBUG_BACKTRACE_IGNORE_ARGS")
?DEBUG_BACKTRACE_IGNORE_ARGS
:FALSE );
$top_frame = array_pop($backtrace);
$top_script_path = $top_frame['file'];
return $top_script_path;
}
function makeRelativePath($from,$to){
// Compatibility
$from = is_dir($from) ?rtrim($from,'\/').'/' :$from;
$to = is_dir($to) ?rtrim($to,'\/').'/' :$to;
$from = str_replace('\\','/',$from);
$to = str_replace('\\','/',$to);
//----------------------------
$from = explode('/',$from);
$to = explode('/',$to);
$path = $to;
foreach($from as $depth => $dir) {
if ($dir === $to[$depth]) { // find first non-matching dir
array_shift($path); // ignore it
} else {
$remaining = count($from)-$depth; // get number of remaining dirs to $from
if ($remaining>1){
// add traversals up to first matching dir
$padLength = -(count($path)+$remaining-1);
$path = array_pad($path, $padLength, '..');
break;
} else {
$path[0] = './'.$path[0];
}
}
}
return rtrim(implode('/', $path),'\/');
}
So, basically, I use the makeRelativePath function to calculate a relative path from the top script's absolute path to the current script's absolute directory path (__DIR__).
I realized that I'm actually looking for the relative path to the library from the top script, not just the parent script -- because the top script is the one where clientside assets will need to be referenced in relation to.
Naturally, PHP doesn't just give you the top script's absolute path. On some environments, the top script's path can be available as a $_SERVER variable, however environment independence is important for me, so I had to find a way.
getTopScriptPath was my solution, as it uses debug_backtrace to find it (creepy, I know), but it is the only environment-independent way to fetch it (to my knowledge).
Still hoping for a more elegant solution, but am satisfied that this one works.
I believe this is what you're looking for:
$this->linkroot = basename(pathinfo($_SERVER['REQUEST_URI'], PATHINFO_DIRNAME));
You can remove basename() to get the full path. Basically, you can run the following code:
echo '<pre>';
var_dump($_SERVER);
echo '</pre>';
If the path you're looking for isn't there in some shape or form, then it simply isn't available and you will have no other choice but to hard code it, or at least hard code part of it.
Have you tried doing a relative link from the root? If not, you might try something like this, if I understand your folder structure.
<link href="/my-library/assets/my-library.css" rel="stylesheet" type="text/css" />
Links like this in any page within your site will pull the same stylesheet, up or down the site structure.
I have an interesting problem. I currently have a basic template library that renders out a bunch of modules for header and footer templates, then sandwiches a view I specify in between them.
Example:
$this->load->view('header.php', $headerstuff);
$this->load->view($contentView);
$this->load->view('footer.php', $footerstuff);
The problem is that I need to put some javascript (that is specific to each content view) into the header. I have been doing this with a switch statement containing the js inside the template library. But that makes no sense in the mvc model.
Example (of what I've been doing), (in template library, above previous code):
$headerstuff['js'] = '';
switch ($contentView)
{
case 'main':
$headerstuff['js'] = 'JAVASCRIPT INCLUDE CODE 1';
break;
case 'product':
$headerstuff['js'] = 'JAVASCRIPT INCLUDE CODE 2';
break;
}
I can't think of another way to do this though. I would like to (ideally) store the js in a variable inside the content view file, and (somehow) load that into the header view. To be honest though, I don't even think that is possible.
Does anybody have a better way of doing this then my current solution?
Thanks,
Max
I created a helper file to do this for my sites and I think we have a similar MVC template layout:
Asset Helper:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
if ( ! function_exists('css'))
{
function css($array) {
// If the object passed is a string, convert it into an array
if ( is_string($array) ) {
$array = explode(" ", $array);
}
// Add additional CSS Files
if ( isset($array) ) {
foreach ( $array as $i => $file ) {
// If it's not the first one add a tab character.
if ( $i > 0 ) echo "\t";
echo "<link rel=\"stylesheet\" type=\"text/css\" href=\"". $file ."\">\n";
}
}
}
}
if ( ! function_exists('js'))
{
function js($array) {
// If the object passed is a string, convert it into an array
if ( is_string($array) ) {
$array = explode(" ", $array);
}
// Add additional JavaScript Files
if ( isset($array) ) {
foreach ( $array as $i => $file ) {
// If it's not the first one add a tab character.
if ( $i > 0 ) echo "\t";
echo "<script src=\"". $file ."\"></script>\n";
}
}
}
}
This does one of two things. I will allow you to add files in the controller file which is nice. I do this by creating a data object:
$data['css'] = array('/path/to/styles.css', '/path/to/otherstuff.css');
$data['js'] = array('/path/to/javascript.js');
Then in your header include do the following:
<?
$defaultCSS = array("/assets/css/global.css");
$css = (isset($css)) ? array_merge($defaultCSS, $css) : $defaultCSS;
css($css);
$defaultJS = array("/assets/js/global.js");
$js = (isset($js)) ? array_merge($defaultJS, $js) : $defaultJS;
js($js);
?>
I'm settings some defaults that will load on each page and then I can add in different files based on which controller I'm loading.
Hope this helps.
How about having a scripts controller which acts as a small proxy:
/**
* A basic controller which allows for the display of basic views.
*/
class Scripts extends CI_Controller
{
public function _remap( $meth, array $params )
{
// ensure that parent directory contents can't be revealed.
if( !$meth || strpos( $meth, '.' ) !== FALSE ) show_404();
foreach( $params as $arg )
// I put all css in a folder called css all js goes into
// a folder called js. It can be overly simple, but it works
// (if you need these files to be php files, just add '.php'
// to the end of the FILE's name. No need to worry about that
// here, CodeIgniter's got your back...)
$this->load->view( "$meth/${params}.$meth" );
}
}
How to use:
For each controller (or, you could easily modify this to be for every method), have an appropriate js file in your views/js folder. Name each after the respective controller. Then, point to each of them from the view:
Then, in the header view:
<script type="text/javascript" src="
<?php
// honestly, I normally use a helper function here, but this is the
// short... short version.
echo base_url() . '/scripts/js/' . load_class('Router')->fetch_class();
// You can also replace the controller name with the view name by using the
// variable $_ci_view, you can get the full path with $_ci_path
?>" ></script>
Best part? Your controller is agnostic about the contents of the view. The view is more or less agnostic of the controller (it is a variable populated ahead of time from an external source and just... there). In fact, the only things which "need to know" are the JS/CSS files and they were on a case by case configuration anyway.
I'm trying to load the google maps API ie:
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=true">
in my head template. But because I've only got one page with a google map on it (I'd rather not have the API load for all files), how would I send the message from the controller through to the view that I wish to load this particular JS file?
Thanks for your help.
CodeIgniter has a segments class. You would be able to run some code like:
<?php if($this->uri->segment(1) == 'map') { ?>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=true">
<?php } ?>
When on page http://yoursite.com/map/ it will load the script.
One solution is to either use a template library that has javascript/css "injection" - see:
http://williamsconcepts.com/ci/codeigniter/libraries/template/reference.html#utilities
$this->template->add_js('js/jquery.js');
$this->template->add_js('alert("Hello!");', 'embed');
for more information.
If you don't want to use a template library, do something like this:
*assuming on the "Map" controller, and that you need the JS file on the default page.
class Map extends CI_Controller {
function __construct()
{
parent::__construct();
}
function index()
{
$scripts = array(
'<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=true">' . "\n",
'<script>something</script>');
/* this method lets you add multiple...*/
$data['scripts'] = $scripts;
$this->load->view('my_map/index', $data);
}
}
in your view:
if(isset($scripts))
{
foreach($scripts as $script)
{
echo $script;
}
}
essentially you build an array of script files/css files (whatever), then check for its prescence and dump it in the head section of your view.
I'd personally go for the template option.
Also note CI2.0 has a new javascript driver might be worth a read
<?php
/**
* Head files loader
* #author azhar
**/
function headscripts($path)
{
if(is_string($path))
{
echo "<script type='text/javascript' src='". base_url($path) ."'></script>\n";
}elseif(is_array ($path)){
foreach ($path as $p) {
echo "<script type='text/javascript' src='". base_url($p) ."'></script>\n";
}
}
}
function headlinks($path)
{
if(is_string($path))
{
echo "<link rel='stylesheet' href='". base_url($path) ."'/>\n";
}elseif(is_array ($path)){
foreach ($path as $p) {
echo "<link rel='stylesheet' href='". base_url($p) ."'/>\n";
}
}
}
?>
Add this file in your helper_directory under the name head_helper. In your controller inside an action use this code
$data['headscripts'] = array('js/main.js');
And in your view file use this function
headscripts($headscripts);
For stylesheet use this
headlinks($headlinks);
And yes do not forget to load the helper using autoload.php file in config folder like this
$autoload['helper'] = array('url', 'file','head');
Thanks for your answers guys, I ended up doing a mixture of Ross and leaf dev's suggestions before I found your answers here, so I guess I was on the right track.
My controller has:
$data['head'] = array('specificjs');
$this->load->view('view',$data);`
and my view has:
if(isset($head)){
foreach($head as $item){
$this->load->view('js/'.$item);
}
}
and my 'specificjs' view has what's required.
This way I can load as many custom scripts as I want and have the code in a view not a controller.
Thanks again, but keep any further suggestions coming!
write a helper for this. Pass the scripts names in an array and inside the helper function iterate over them and print the scripts