CUTCODEDOWN
Minimalist Semantic Markup

Welcome Guest
Please Login or Register

If you have registered but not recieved your activation e-mail in a reasonable amount of time, or have issues with using the registration form, please use our Contact Form for assistance. Include both your username and the e-mail you tried to register with.

Author Topic: Squire 3 / Paladin X.3 -- Rebuilding from the ground up.  (Read 10036 times)

Jason Knight

  • Administrator
  • Hero Member
  • *****
  • Posts: 1049
  • Karma: +188/-1
    • CutCodeDown -- Minimalist Semantic Markup
Re: Squire 3 / Paladin X.3 -- Rebuilding from the ground up.
« Reply #30 on: 15 Nov 2020, 11:06:57 am »
/libs/common.lib.php -- common library functions and methods

Code: [Select]
function cleanString($string) {
return htmlspecialchars(strip_tags($string));
}

function cleanPath($path) {
return trim(str_replace(['\\', '%5C'], '/', $path), '/');
}

Just some common oft used string cleaners.

Code: [Select]
/*
A more robust version of hashCreate should allow for multiple hashes
to be stored of the same name, with an expiration time on it.
*/

function hashCreate($name) {
return $_SESSION[$name] = bin2hex(random_bytes(24));
}

function hashExists($name, $hash) {
return isset($_SESSION[$name]) && ($_SESSION[$name] == $hash);
}

Generates a random hash, or checks for the existence of one. I may make this a more robust Object in Paladin to show what I'm talking about with the comment.

Code: [Select]
function httpError($code) {
include('fragments/httpError.php');
}

function paladinError($title, $desc) {
include('fragments/paladinError.php');
}

One of the few cases where a direct call is allowed to output, though in most cases it would throw an error since the template wouldn't be loaded.

These are dynamically loaded AS NEEDED so that we're not wasting time loading their entire code even when unused. That's a mistake I see in a lot of off the shelf CMS and frameworks is that they'll load full on handlers with their output, even when they're not called.

Code: [Select]
function uriLocalize($uri) {
if (
substr($uri, 0, 4) == 'http' ||
substr($uri, 0, 1) == '#'
) return $uri;
return ROOT_HTTP . $uri;
} // uriLocalize

Handy when working with URI's that you don't know how/where they're rooted, to either plugin or not plugin the full local file path. Used extensively in the template, it can also be useful for in-content links, particularly if trying to avoid full-blown bloated absolute URI's.

Code: [Select]
function templateLoad($name, $actionPath = 'template/default') {
if (
file_exists($fn = TEMPLATE_PATH . $name . '.template.php') ||
file_exists($fn = $actionPath . $name . '.template.php')
) include_once($fn);
else {
error_log(
'unable to find template file for "' . $name . '"'
);
die('template error');
}
} // templateLoad

As you can see this cascades through TEMPLATE_PATH and the optional $actionPath to try and find the corresponding file.

AND YES those are single = as we're testing on assignment! Seriously folks, =, ==, and === are three separate things for a reason, HOW DARE we actually use them for those reasons because a handful of nose-breathers are too stupid to understand them!

The error handling would/should be more robust akin to httpError once we get to "paladin".

Code: [Select]
/*
safeInclude is utterly dumbass, but since PHP just LOVES to
bleed scope all over the place with 1970's style "includes"
we have to do this JUST to break local scope!
*/
function safeInclude($file) {
include($file);
}
Seriously, just read the comment.

Code: [Select]
final class Request {

private static
$data = false,
$path = '';

private static function set() {
self::$path = parse_url(cleanPath($_SERVER['REQUEST_URI']), PHP_URL_PATH);
if (strpos(self::$path, '..')) die('Hacking Attempt Detected, Aborting');
self::$path = substr(self::$path, strlen(ROOT_HTTP) - 1);
self::$data = (
empty(self::$path) ?
[ Settings::get('default_action') ] :
explode('/', self::$path)
);
foreach (self::$data as &$p) $p = urldecode($p);
} // Request::set

public static function value($index = 0) {
if (!self::$data) self::set();
return isset(self::$data[$index]) ? self::$data[$index] : false;
} // Request::value

public static function getPath() {
if (count(self::$data) == 0) self::set();
return self::$path;
} // Request::getPath

} // Request

Self initializing "static object", handles parsing $_SERVER['REQUEST_URI']. Notice that it strips out encoded/incorrect slashes and die's if it's a mismatch. Another die is performed if a ".." is included in the name so as to disallow up-tree hack attempts.

From there it just guts out the ROOT_HTTP (see below) for the basepath, explodes if appropraite, and then makes sure a urldecode is performed on each sub-chunk.

The value() routine is a getter, and getpath is handy occasionally.

Code: [Select]
final class Settings {

private static $privateData = [];

public static $publicData = [];
This one's a bit large so I'm going to just summarize by function name.

First up we have private and public data. The private ones should be for things you don't want normal code to even think about having access to changing.

Code: [Select]
public function loadFromIni(...$files)
Loads ini file data into $privateData or the appropriate public subsections and/or define(). Will overwrite existing private.

Code: [Select]
public static function get($name, $section = false)
Pulls private data as the priority, otherwise returns public, checking for sections or root as appropriate.

Code: [Select]
public static function set($value, $name, $section = false)
The setter can only set public data.

Code: [Select]
define(
'SCRIPT_PATH',
cleanPath(pathinfo($_SERVER['SCRIPT_NAME'], PATHINFO_DIRNAME))
);

SCRIPT_PATH is the directory path our script is in, but "cleaned" and having the name of the script ripped off it. Handy in a few cases, but mostly a prelude to:

Code: [Select]
define(
'ROOT_HTTP',
'/' . SCRIPT_PATH . (SCRIPT_PATH == '' ? '' : '/')
);

Which we "need" to prefix to local URI's for things like stylesheet LINK, script src="", and so forth.

... and that's the common.lib.php

I'll cover the sample /action/ next.
We are all, we are all, we are all FRIENDS! For today we're all brothers, tonight we're all friends. Our moment of peace in a war that never ends.

Jason Knight

  • Administrator
  • Hero Member
  • *****
  • Posts: 1049
  • Karma: +188/-1
    • CutCodeDown -- Minimalist Semantic Markup
Re: Squire 3 / Paladin X.3 -- Rebuilding from the ground up.
« Reply #31 on: 15 Nov 2020, 11:45:33 am »
/actions/static/static.startup.php -- a sample "process"

Startup functions are our "process", they take the already parsed user request and use it to either perform the requested action, or retrieve the requested data.

All action files should have a action_startup() function in them that returns an array of data. In this case our function:

Code: [Select]
if (!($page = Request::value(1))) {
$page = Request::value();
if (!$page || ($page == 'static')) $page = 'default';
}
if there's no second value in the URI (index 1) we see if there's a page that's the same as the request index 0. This allows for static pages to be called as /static/pageName or /pageName so long as page names don't match any actions. If this functionality is used, you may want to add a canonical <link> in the markup!

If it's not present either way, we use the 'default'.

We then set up the default call values:

Code: [Select]
$data = [
'contentFilePath' => 'actions/static/pages/' . $page . '/' . $page,
'pageName' => $page
];

$dataSources:
Code: [Select]
$dataSources = 0;

Is a counter of if a settings ini or a startup.php are loaded.

Code: [Select]
if (file_exists($fn = $data['contentFilePath'] . '.ini.php')) {
$dataSources++;
Settings::loadFromIni($fn);
}

We load any appropriate settings for the content into Settings

Code: [Select]
if (file_exists($fn = $data['contentFilePath'] . '.startup.php')) {
$dataSources++;
safeInclude($fn);
$data = array_merge(
$data,
static_startup()
);
}

and we run any startup for said page. Note that both can be done.

If neither ran:
Code: [Select]
if (!$dataSources) httpError(404);
Then we can assume a 404.

Otherwise:
Code: [Select]
return $data;

Now, once a database is involved we will be passing that around, so the declaration will change to:

Code: [Select]
function action_startup($db) {
So that it can pass the database to static_startup in any /actions/static/pages/pageName/pageName.php

Or in the case of other pages, use it itself to do database processing. Passing $db in this manner isolates its scope so that things which have ZERO blasted business accessing the database -- like the template -- have no access to it.

Remember, database connection in global scope is 100% hurr-durrz ermahgahd aherpaderp!

Now we have two different types of default pages setup, a "test" and a "default". Let's look at:

/actions/static/pages/default/default.ini.php

Code: [Select]
; <?php die(); // prevent direct calls just in case

currentPage "Home"

[meta]
keywords[name] = "keywords"
keywords[content] = "Default, Template, Poor, Man, Content, Management"

description[name] = "description"
description[content] = "Default Demo for Poor Man's Content Management"

The first line as already discussed prevents the .ini file from outputting anything if called directly via http just in case our rewriteRule goes awol.

We set the currentPage which is used to match the menu item in the template

Then some simple metadata as appropriate to the content.

The default page uses a .static file which is just static HTML content that will be loaded via readFile. Such statics are "ultra safe" because they cannot contain PHP.

The test on the other hand

/actions/static/pages/test/test.ini.php

Code: [Select]
; <?php die(); // prevent direct calls just in case

currentPage "Test"
noExtras true

Says "noExtras" to make the sidebars be hidden in the result, and it lacks any metadata.

It uses an actual:

/actions/static/pages/test/test.content.php

which defines our action_content that echo's out the result. The demo version just vomits up markup, but in practice action_content would be passed $data and the PHP would be used to call the template, gluing our $data to the markup as appropriate.

So a more proper version would go something like this:

Code: [Select]
function action_content($data) {

  // process and output the data here, calling the appropriate template functions.

} // action_content

In a nutshell, that's 90%+ of how squire is used to build a poor man's CMS and lays the groundwork for more complex systems.
We are all, we are all, we are all FRIENDS! For today we're all brothers, tonight we're all friends. Our moment of peace in a war that never ends.

Dave

  • Junior Member
  • *
  • Posts: 38
  • Karma: +12/-0
Re: Squire 3 / Paladin X.3 -- Rebuilding from the ground up.
« Reply #32 on: 17 Nov 2020, 09:44:22 am »
Early on in this thread GrumpyYoungMan asked
Quote
This code looks as it is something I could tweak to suit my needs - Am I free to do that and use your code?
I didn't see an answer but I have the same question. This looks perfect for a small side project I'm working on and would like to use it too,.
Dave

John_Betong

  • Full Member
  • ***
  • Posts: 218
  • Karma: +24/-1
    • The Fastest Joke Site On The Web
Re: Squire 3 / Paladin X.3 -- Rebuilding from the ground up.
« Reply #33 on: 17 Nov 2020, 10:05:57 am »
@Dave,
There has been no objections to abusing the script on my site:


https://thisisatesttoseeifitworks.tk/deathshadow/static/info


As mentioned I am concerned at how closely knit the script is and updates will be very difficult. I sincerely hope I am wrong and looking forward to the conversion to a blog site.
Retired in the City of Angels where the weather suits my clothes

Jason Knight

  • Administrator
  • Hero Member
  • *****
  • Posts: 1049
  • Karma: +188/-1
    • CutCodeDown -- Minimalist Semantic Markup
Re: Squire 3 / Paladin X.3 -- Rebuilding from the ground up.
« Reply #34 on: 17 Nov 2020, 10:34:12 am »
Early on in this thread GrumpyYoungMan asked
Quote
This code looks as it is something I could tweak to suit my needs - Am I free to do that and use your code?
I didn't see an answer but I have the same question. This looks perfect for a small side project I'm working on and would like to use it too,.
I thought I answered that, might have gotten lost in the other thread during the split.

I don't put code up online for people NOT to use. Consider the license -- for the time being -- as identical to that of SQLite... since to me, well... read the disclaimer/license I've released other software under:

Quote
Source Code © Jason M Knight and released to the public domain. If you are going to give something away, lands sake just GIVE IT AWAY!!!. Don't give me none of that dirty hippy "open source" nonsense!

Here's a tip: If someone starts running their mouth about "Freedom", only to then weigh it down with a licensing agreement larger than the founding documents of most nations; placing restrictions on what you can and cannot do with it by way of loopholes in contract law and legalese nobody but a business lawyer can decipher?

Well, does the term "snake oil" ring a bell?

In case you couldn't tell, not a fan of the FSF or the GPL. Stallman and his unwashed incels can **** right off.
We are all, we are all, we are all FRIENDS! For today we're all brothers, tonight we're all friends. Our moment of peace in a war that never ends.

benanamen

  • Full Member
  • ***
  • Posts: 188
  • Karma: +18/-0
Re: Squire 3 / Paladin X.3 -- Rebuilding from the ground up.
« Reply #35 on: 17 Nov 2020, 11:36:15 am »
Thanks for the breakdown commentary. Very interested in seeing your Database implementation.

Questions:

1. What is Paladin you keep mentioning.
2. Can you please provide more info and simple code examples on function safeInclude showing the "problem" without it and how it solves that problem with it.
To save time, let's just assume I am never wrong.

benanamen

  • Full Member
  • ***
  • Posts: 188
  • Karma: +18/-0
Re: Squire 3 / Paladin X.3 -- Rebuilding from the ground up.
« Reply #36 on: 17 Nov 2020, 03:11:53 pm »
Squire3 is on my Github at https://github.com/benanamen/squire3
To save time, let's just assume I am never wrong.

Jason Knight

  • Administrator
  • Hero Member
  • *****
  • Posts: 1049
  • Karma: +188/-1
    • CutCodeDown -- Minimalist Semantic Markup
Re: Squire 3 / Paladin X.3 -- Rebuilding from the ground up.
« Reply #37 on: 17 Nov 2020, 08:05:13 pm »
1. What is Paladin you keep mentioning.
Squire is NOT the system you see running, but a subsection of it ... the specific functions I believe I outlined. It is the closest thing I have to a framework, though it's really more of just a helper library.

Paladin is what it's called once it becomes a full CMS with database and the like.

Can you please provide more info and simple code examples on function safeInclude showing the "problem" without it and how it solves that problem with it.

The "problem" is that PHP bleeds scope like a pig. Wherever you do an include or require, it behaves as if the code you're including is right there in the code, in the same scope.

testInclude.php
Code: [Select]
<?php
echo $test;

test1.php
Code: [Select]
<?php
$test 
"something";
include(
'testInclude.php'); // outputs "something"

test2.php
Code: [Select]
<?php
$test 
"something";
function 
safeInclude($file) {
  include(
$file);
}
safeInclude('testInclude.php'); // throws an error, $test doesn't exist

because safeInclude is its own function with no global statement, it breaks scope. This means that we can use it to include files in places we don't want variables like $db to be blindly passed to them, limiting the number of places "bad things" can happen with code elevations.

Even though like all interpreted scripting source-distributed languages, PHP is an insecure wreck on that front, that doesn't mean we don't add multiple layers of code protections.

It's a royal pain in the ass and the biggest security hole in PHP that there's no PROPER library/unit inclusion, and that all file operations are allowed to blindly read/write source files. fopen/readfile/etc, etc, should all have been restricted from reading and writing .php or some other extension so that they can only be loaded as libs, with a mechanism JUST for library and perhaps another for just secure data.

Conceptually it's a bit like the "chicken and the echo" problem. Ever seen that one?

Code: [Select]
function test() { echo 'test.' }

echo 'This is a ' . test() . '<br>'; // outputs "test.This is a <br>"
echo 'This is a ', test(), '<br>'; // outputs "This is a test.<br>"

And people wonder why I favor comma delimits :D
We are all, we are all, we are all FRIENDS! For today we're all brothers, tonight we're all friends. Our moment of peace in a war that never ends.

Jason Knight

  • Administrator
  • Hero Member
  • *****
  • Posts: 1049
  • Karma: +188/-1
    • CutCodeDown -- Minimalist Semantic Markup
Re: Squire 3 / Paladin X.3 -- Rebuilding from the ground up.
« Reply #38 on: 17 Nov 2020, 09:24:34 pm »
Ok, as this matures from Squire to Paladin, there's a few extra things to do first.

As a few folks have asked, we'll set up for language strings. These are common interface values or messages that we might want to internationalize.

Code: [Select]
final class Lang {

private static $cache = [];

public static function get($longName) {

if (substr($longName, 0, 1) !== '@') return $longName;

$splode = explode('_', substr($longName, 1), 2);
$name = $splode[0];
$module = count($splode) > 1 ? $splode[1] : 'common';
$lang = Settings::get('lang') ?: 'en';

if (!array_key_exists($module, self::$cache)) {
if (file_exists(
$fn = 'lang/' . $lang . '/' . $module . '/' . $module .  '.ini.php'
)) self::$cache[$module] = parse_ini_file($fn);
else self::$cache[$module] = [];
}

if (
array_key_exists($name, self::$cache[$module])
) return self::$cache[$module][$name];

if (file_exists(
$fn = 'lang/' . $lang . '/' . $module . '/' . $name . '.txt'
)) return self::$cache[$module][$name] = file_get_contents($fn);

error_log(
'Paladin Error -- Lang::get, key "' . $name .
'" not found in module "' . $module
);

return '<strong style="color:red; font-weight:bold;">LANG[' . $longName . ']</strong>';

} // Lang::__get

} // Lang

The format for calliing Lang would go something like this:

Lang::get('@title_loginModal');

If the global language is set to "en" it will return the name before the underscore in /lang/en/loginModal/loginModal.ini.php, or the contents of the text file /lang/en/loginModal/title.txt

Said values are cached should you decide to use them twice. An example .ini goes something like this:

Code: [Select]
; <?php die();

title "Log In"
username "Username"
password "Password"
submit "Submit"

If you wanted to add french to the system:

/lang/fr/loginMOdal/loginModal.ini.php
Code: [Select]
; <?php die();

title "S'identifier"
username "Nom d'utilisateur"
password "Mot de passe"
submit "Soumettre"

So anyplace you have UI elements you want to internationalize, you use Lang:: to pull the corresponding string.

Be aware that:

1) if a language string is not found, a styled STRONG tag is returned of the name. Normally I rail against the use of style="" in all but a handful of corner cases. Hello Mr. Corner Case.

2) Should you feed it a string without the @, it will return the normal string. This can be handy during prototyping.

3) if you @ but without the underscore, "common" will be used instead. Hence "@test" would be the same as @test_common

Next on deck is "Bomb". It's a static function that will wrap all our different throw, loading their code only as needed. Basically httpError gets folded into this as well as the native system. Think of Bomb as being like a "die" that loads the template and logs the error.

Code: [Select]
/*
Bombs are fatal errors, execution will end after message
is logged and displayed.
*/

final class Bomb {

public static function http($code) {
include('fragments/httpError.php');
} // Bomb::http

public static function paladin($title, $desc) {
include('fragments/paladinError.php');
} // Bomb::paladin

} // Bomb

This way we only have a single static polluting the namespace.

httpError you already have, paladinError looks similar.

/fragments/paladinError.php
Code: [Select]
<?php
/*
ASSUMES
$title
$desc
*/

Settings::set('Paladin System Error''pageTitle');
Settings::set(true'noExtras');

template_header();

error_log('Paladin Error - ' $title ' - ' $desc);

echo 
'
<section class="httpError">
<div>
<h2>'
Lang::get('@title_errors'), '</h2>
<p>
'
Lang::get('@systemError_errors'), '
</p><p>
'
Lang::get('@errorHasBeenLogged_errors'), '
</p>
</div>
</section>'
;

template_footer();

die();

Note that the error is NOT shown client side, and is logged instead. Bomb should be used for error conditions that could bleed information client-side hackers could exploit, like a database connection failure.

The corresponding language file being:

/lang/en/errors/errors.ini.php
Code: [Select]
; <?php die();

title "Paladin Error"
systemError "A system error occurred. Please try again. If the problem persists please contact the administrator."
errorHasBeenLogged "This error has been logged"

It's also time to lay out the database structures. For now we'll do users, user_permissions,

For users let's go basic:

Code: [Select]
CREATE TABLE users (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(128) UNIQUE,
username VARCHAR(64) UNIQUE,
password VARCHAR(64),
register_email VARCHAR(320),
contact_email VARCHAR(320),
created DATETIME DEFAULT CURRENT_TIMESTAMP
last_access DATETIME DEFAULT CURRENT_TIMESTAMP
INDEX (name, username, last_access)
)

Name being the "display name" used in output, which can be different from the login username. Password is big enough to accept 512 bit ciphers, and of course some standard timestamps.

For user permissions:

Code: [Select]
CREATE TABLE user_permissions (
id BIGINT,
permission VARCHAR(127),
filter TINYINT DEFAULT 0
INDEX (id, permission)
)

Permission is the text name of the permission, such as "Can Write Blogs" or "Can Edit Users". Filter is one of -1, 0, or 1, representing prevent, no, and yes respectively.

When/if we get into user groups, they too will have permissions (groups_permissions) that will have filters. The filters will be binary "and" together. To test if a user has the permission, we'll simply "$user->hasPermission('Can Tell Users to Sod Off') > 0"

For those confused, "prevent"  means the user is never allowed to have the permission, regardless of what other user groups or even the user's own permissions say.

Ok, the next step is to get a setup routine going. The setup will work in stages, use sessions and hashes to verify installation steps as sent client-side, be password locked off a value in the user.config.ini.php, etc, etc, etc...

I always build a proper installer at the start instead of dicking around in phpMyAdmin or at the command line. You're going to need it anyways, so just DO IT!
We are all, we are all, we are all FRIENDS! For today we're all brothers, tonight we're all friends. Our moment of peace in a war that never ends.

benanamen

  • Full Member
  • ***
  • Posts: 188
  • Karma: +18/-0
Re: Squire 3 / Paladin X.3 -- Rebuilding from the ground up.
« Reply #39 on: 17 Nov 2020, 09:50:11 pm »
Thanks for clarifying with code on the safeInclude function.

Quote
Wherever you do an include or require, it behaves as if the code you're including is right there in the code, in the same scope.

That is the exact behavior I want to separate parts. I would like to see use cases where I would actually need the safeInclude function.

Example

<?php

include 'header.php';
include 'nav.php';
include 'content.php';
include 'footer.php';


Quote
the "chicken and the echo" problem

This is interesting that it works like it does. Can you explain why it does what it does?

EDIT: Ok, I see why this "problem" exists. In the function, you are echoing the text "test" instead of returning it. If you return the text then all the examples work as they should.

The periods are concatenation. The commas are argument separators just like in functions. The arguments are written to the output in the same order as they are passed in.

With your first example, the period has precedence so the function right after it is run first, then everything else is output.

I would have to call this a good example of bad programming.

Providing arguments for echo is faster, so you still have a win there.

This works in both examples using return:
Code: [Select]
function test() { return 'test.'; }

echo 'This is a ' . test() . '<br>'; // outputs "This is a test.<br>"
echo 'This is a ', test(), '<br>';   // outputs "This is a test.<br>"


Your example could very well have been written like this for more clarification of operator precedence. The output is exactly the same.
Code: [Select]
echo 'This is a <br>' . test() ; // outputs "test.This is a <br>"
This "problem" is simply about operator precedence and echoing a function output instead of returning it.



« Last Edit: 17 Nov 2020, 11:30:21 pm by benanamen »
To save time, let's just assume I am never wrong.

John_Betong

  • Full Member
  • ***
  • Posts: 218
  • Karma: +24/-1
    • The Fastest Joke Site On The Web
Re: Squire 3 / Paladin X.3 -- Rebuilding from the ground up.
« Reply #40 on: 17 Nov 2020, 11:40:45 pm »

Conceptually it's a bit like the "chicken and the echo" problem. Ever seen that one?

Code: [Select]
function test() { echo 'test.' }

echo 'This is a ' . test() . '<br>'; // outputs "test.This is a <br>"
echo 'This is a ', test(), '<br>'; // outputs "This is a test.<br>"

And people wonder why I favor comma delimits :D

This appears to be legacy code and not taking advantage of PHP 7.0 Declare((strict_types=1);

When using declare(strict_types=1); an error will be thrown because function test(); is not a string. The type can be found using gettype();

Without using strict_types=1 PHP will not bother checking types and no errors will be thrown. Type juggling  will try guessing the type.

Why is strict_types=1 never used in this current project?

Please note that I am currently unable to test because I am in the process of resurrecting  my OS :(

Retired in the City of Angels where the weather suits my clothes

Jason Knight

  • Administrator
  • Hero Member
  • *****
  • Posts: 1049
  • Karma: +188/-1
    • CutCodeDown -- Minimalist Semantic Markup
Re: Squire 3 / Paladin X.3 -- Rebuilding from the ground up.
« Reply #41 on: 18 Nov 2020, 12:04:25 am »
Why is strict_types=1 never used in this current project?
Because it's mostly pedantic bullshit and shoe-horning of programming concepts that have ZERO business in an interpreted scripting source-distributed language?

It offers ZERO legitimate benefits, and in many ways defeats the entire reason PHP is superior to other languages when it comes to gluing data to markup. Type insensitivity may be a bitch for logic, but it's a blessing when dealing with output.

And used "properly" that's the most PHP should be doing. Processing the user request, interacting with the database, and outputting the results by gluing data to markup. It excels at this and adding strict typecasting pisses on that.

FFS if people want it to behave like C++ or C#, why the **** aren't they just using C++ or C#?!?

Seriously, if I wanted to use a language that behaved that way, I'd be using Ada or even FPC. I'm not, I'm using PHP, so I expect it to behave like an interpreted scripting language should.
We are all, we are all, we are all FRIENDS! For today we're all brothers, tonight we're all friends. Our moment of peace in a war that never ends.

benanamen

  • Full Member
  • ***
  • Posts: 188
  • Karma: +18/-0
Re: Squire 3 / Paladin X.3 -- Rebuilding from the ground up.
« Reply #42 on: 18 Nov 2020, 12:16:03 am »
When using declare(strict_types=1); an error will be thrown because function test(); is not a string.

NO! It is because Jason forgot a semi-colon at the end of the echo. NOTHING whatsoever to do with strict types.

I am surprised after all this time you STILL dont understand strict_types.
« Last Edit: 18 Nov 2020, 12:25:49 am by benanamen »
To save time, let's just assume I am never wrong.

benanamen

  • Full Member
  • ***
  • Posts: 188
  • Karma: +18/-0
Re: Squire 3 / Paladin X.3 -- Rebuilding from the ground up.
« Reply #43 on: 18 Nov 2020, 12:20:43 am »
Quote
Wherever you do an include or require, it behaves as if the code you're including is right there in the code, in the same scope.

Followup...

This is the EXACT purpose of include/require. Per the manual..
Quote
The include statement includes and evaluates the specified file.

Quote
When a file is included, the code it contains inherits the variable scope of the line on which the include occurs. Any variables available at that line in the calling file will be available within the called file, from that point forward.

That pretty much blows out your claim that an include shouldn't output anything.

https://www.php.net/manual/en/function.include.php

« Last Edit: 18 Nov 2020, 12:28:11 am by benanamen »
To save time, let's just assume I am never wrong.

Jason Knight

  • Administrator
  • Hero Member
  • *****
  • Posts: 1049
  • Karma: +188/-1
    • CutCodeDown -- Minimalist Semantic Markup
Re: Squire 3 / Paladin X.3 -- Rebuilding from the ground up.
« Reply #44 on: 18 Nov 2020, 12:50:50 am »
That is the exact behavior I want to separate parts. I would like to see use cases where I would actually need the safeInclude function.

Example

<?php

include 'header.php';
include 'nav.php';
include 'content.php';
include 'footer.php';
That example is NOT something I would do, since it looks like "blind includes". Do those include contain functions, or are they spitting out markup directly without functions?

Really there's NO reason for them to be separate includes, and I would suspect you've got ZERO scope isolation.

Hmm... the database connection in the next release of this is a very good usage case. Let's take some features from main() which replaces the IIFE in index. (see previous post about the new index.php)

It connects to the DB
Code: [Select]
$db = new Database($dsn, $username, $password, $options);

Later on the same function loads the content outputter:

Code: [Select]

if (file_exists($fn = $data['contentFilePath'] . '.content.php')) {
safeInclude($fn);
action_content($data);
}

That PHP file has ZERO business having access to the database handler. None. Nada, Zip, Zilch. Given how easy code appendages are (thanks to the F***wit ability of PHP to fopen source files) and how often they are a "non-destructive avenue of attack", restricting access to the gooey bits and adding re-use prevention is essential to at least make them work harder for it.

Since any idiot can fopen "a", modifying the existing code or supplanting it takes a bit more work. It's not a great answer, but with PHP's "insecure by design" methodology, we do what we can do.

EDIT: Ok, I see why this "problem" exists. In the function, you are echoing the text "test" instead of returning it. If you return the text then all the examples work as they should.

The periods are concatenation. The commas are argument separators just like in functions. The arguments are written to the output in the same order as they are passed in.

With your first example, the period has precedence so the function right after it is run first, then everything else is output.

I would have to call this a good example of bad programming.
It's not always. In fact if you understand the underlying mechanism if all you're going to do is echo the result, it can be more efficient on memory use since you're not expanding the stack or lobbing the result onto the heap.

But it's a matter of do you want to be a pedantic ass willing to slop out a massive memory footprint for the name of some esoteric bullshit, or do you want efficient code. Though again, I suspect that's my inner machine language coder talking. There's a  LOT of things high level language dev's say that make me go "dafuq u say?"

Generally though yes, it's a construct one would not use without very good reason. It's more of a mental exercise to illustrate why string addition (concatenation my ass) on echo is an inefficient construct, painfully so if you look at memory usage. It's not "operator precedence" apart from the fact the string HAS to be COMPLETELY built before it can be passed to echo!

When you use a string addition the ENTIRE string has to be built in memory before it can be passed to the result. Be that result a variable, a function, or a language construct like echo.

That means you have to malloc a "guess' as to how big the result is going to be, it means you might have to "grow" using pointers or reallocate to a new larger memory pointer (the latter wasting time copying your existing values again and leading to memory fragmentation). EVERYTHING has to be copied into this new result before it can be passed along.

Whereas if you comma delimit echo, static pointers to the static strings and dynamic pointers to the variables can just be passed in sequence, requiring none of the mental masturbation and memory thrashing idiocy that string addition brings to the table. Inf act, this is why most of the string heavy string-based string processing "template" systems like Smarty are bloated inefficient crap, no matter how much it mollycoddles "front end developers" who to be frank, don't know enough about the back end to be front-ending jack!

About two years ago I had a client where their site was killing their high end hosting despite traffic numbers a cheap VPS could have handled. It was constantly choking out on RAM, and the reason was obvious to me, but invisible to most. String addition.

They had constructed their PHP output to be one giant ternary operation as a single string added echo. The result was that their bloated 240k of markup was taking a megabyte or more to process. Now, a megabyte might seem like piss in a bucket in the age of gigabytes of RAM... but that's a megabyte of string processing, multiple stack/heap grows, endless copying of that megabyte of strings around, and you multiply that against a hundred thousand requests an hour, and we're talking terrabytes of memory thrashing over that time period.

Just switching it to separate statements and comma delimited echo's dropped the memory footprint by a factor of four and processing time by 3. Of course once I replaced their nutjob 240k of bloat with the 28k needed to do the job, they were able to switch from a big fancy expensive dedicated server to a $20/mo VPS.

And in that way, calling a function to output something from inside an echo can in fact result in lower memory use. I mean in practice it's NO different (given how null is echo'd) from:

echo 'This is a ';
test();
echo '<br>';

It's just a cleaner simpler syntax. It can also be a handy construct in template separation.

Code: [Select]
echo test($id, $db) ? '<i class="fas fa-lock-open"></i>' : '<i class="fas fa-lock"></i>';
Code: [Select]
function test($id, $db) {
  $stmt = $db->prepare('SELECT text, statusCode FROM status WHERE id = ?');
  $stmt->execute([$id]);
  if ($row = $stmt->fetch()) {
    echo $row['text'];
    return $statusCode > 0;
  }
  echo sprintf(Lang::get('failed_status'), $id);
  return false;
} // test

Test may echo any of a dozen different text values, but it returns true or false so we can show the correct icon SEPARATELY from the content text. That way the template can set the icon without care for what text is echo'd out by the function.

Consider it akin to how you can leverage boolean false with === on a return value vs. string where a empty string is a valid value, or multi-handle parameter types (like echo does).
We are all, we are all, we are all FRIENDS! For today we're all brothers, tonight we're all friends. Our moment of peace in a war that never ends.

 

SMF spam blocked by CleanTalk

Advertisement