12. PHP menu & CRUD code skeleton (B12phpfw) comparison

Featured

Revised November 2021 October 2020, December 2019. Original post was published in February 2019.
Download https://github.com/slavkoss/fwphp/ . My articles before this are not needed to learn B12phpfw.

November 2021 version 8.0.0 (requires PHP ver. >= 7). Download  contained many learning examples, but no more because three modules – packages :  Mnu (menu code skeleton), Msg (Blog – compound module) and Mkd  contain enough (not to simple)  PHP code for learning, create large sites CRUD pages and own WYSIWYG web editor. To simple learning code (frequent in learning sources books and internet) is dangerous, leads to wrong coding, which is one of reasons why I have 8 versions. Version 8 is in my opinion smallest code to learn serious web programing.

What’s new in version 8 :

  1. Autoload.php is refactored, much simpler, see old z_NOT_USED_TO_COMPLICATED_Autoload_1stVersion.php
    This is good case showing that first code versions are to complicated code – very frequent case – which is one of reasons why I have 8 versions.
  2. Shares are not any more in \zinc folder but in in vendor folder eg \vendor\b12phpfw\Autoload.php (below web server doc root eg J:\awww\www\). Site roots are on same place in folders hierarchy eg \fwphp (may be named site_x). Also group of modules eg \fwphp\glomodul. Also modules eg \fwphp\glomodul\blog – dir like oracle Forms form module.
  3. PHP 8 (ver. 7 still works) and Bootsrap 5
<?php
http://dev1:8083/fwphp/www/
// J:\awww\www\fwphp\www\index.php
namespace B12phpfw\site_home\www ;

use B12phpfw\core\b12phpfw\Autoload ;

//1. settings - properties - assign global variables to use them in any code part
$module_path = str_replace('\\','/', __DIR__) .'/' ;
$site_path = dirname($module_path) .'/' ; //to app dir eg "glomodul" dir and app
//to web server doc root or our doc root by ISP $module_towsroot = eg '../../../'
$wsroot_path = dirname(dirname($module_path)) .'/' ;
//or $wsroot_path = str_replace('\\','/', realpath('../../')) .'/' ;
$shares_path = $wsroot_path.'vendor/b12phpfw/' ; //includes, globals, commons, reusables

$pp1 = (object)
[ 'dbg'=>'1', 'stack_trace'=>[str_replace('\\','/', __FILE__ ).', lin='.__LINE__]
, 'module_version'=>'8.0.0.0 Mnu' //, 'vendor_namesp_prefix'=>'B12phpfw'
// $_SESSION["TrackingURL"]=$_SERVER["PHP_SELF"];
// 1p. (Upper) Dirs of clsScriptsToAutoload. With 2p(ath). makes clsScriptToAutoloadPath
// 2p. Dir name of clsScriptToAutoload is last in namespace and use (not full path !).
, 'wsroot_path' => $wsroot_path // to awww/www (or any names)
, 'shares_path' => $shares_path // to b12phpfw, b12phpfw is required dir name
, 'site_path' => $site_path // to fwphp (or any names)
, 'module_path' => $module_path // to fwphp/www (or any names)
//
, 'glomodul_path' => $site_path .'glomodul/'
, 'examples_path' => $site_path .'glomodul/z_examples/'
] ;

//2. global cls loads classes scripts automatically
require($pp1->shares_path . 'Autoload.php');
new Autoload($pp1);

//3. process request from ibrowser & send response to ibrowser :
//Home_ ctr "inherits" index.php ee inherits $p p 1
$module = new Home_ctr($pp1) ; //also instatiates higher cls : Config_ allsites

exit(0);
<?php
// J:\awww\www\vendor\b12phpfw\Autoload.php
namespace B12phpfw\core\b12phpfw ; //Dir name is last in namespace and use 
//use B12phpfw\dbadapter\post\Tbl_crud ; // as Tbl_crud_post ;

class Autoload
{
protected $pp1 ; //M O D U L E PROPERTIES PALLETE like in Oracle Forms

public function __construct(object &$pp1) {
$pp1->stack_trace[]=str_replace('\\','/', __FILE__ ).', lin='.__LINE__ .' ('. __METHOD__ .')';
$this->pp1 = $pp1 ;
spl_autoload_register(array($this, 'autoloader'));
return null ;
}

function autoloader(string $nscls) 
{
//$nscls is namespaced called cls name eg B12phpfw\module\book\Home_ctr
$DS = DIRECTORY_SEPARATOR ;
$nscls_linfmt = str_replace('\\',$DS, $nscls) ; //ON LINUX
$clsname = basename($nscls_linfmt) ; //eg Home_ctr, Config_ allsites, Db_allsites
$module_dir = basename(dirname($nscls_linfmt)) ; //eg Home_ctr, Config_ allsites, Db_allsites
//echo '<pre>$nscls='; print($nscls) ; echo ' $module_dir='; print($module_dir) ; echo '</pre>';

switch ($module_dir) {
case 'b12phpfw': 
$clsscript_path=dirname($this->pp1->shares_path).'/'.$module_dir.'/'.$clsname .'.php' ; 
break;
default:
$clsscript_path=dirname($this->pp1->module_path).'/'.$module_dir.'/'.$clsname .'.php' ; break;
break; 
}
//echo '<p>$clsscript_path='; print($clsscript_path) ; echo ' SAYS: '. __METHOD__ .'</p>';

require $clsscript_path;


}
}

2020.09.30 DONE version 7.0.0 (declare(strict_types=1);).

DBI is improved with trait Db_allsites instead class Db_allsites. Each DB (persistent storage) adapter-class named Tbl_crud :

  1. use B12phpfw\core\zinc\Db_allsites
  2. implements Interf_Tbl_crud

This means that :

  1. eg blog module in blog folder, works much easier with more model classes ee Tbl_crud -s, ee with own Tbl_crud and with other tables Tbl_crud.
  2. eg sole module controller class Home_ctr extends class Config_allsites, no more extends also two DB CRUD classes which is unnatural (but seems easy because logically : all is in Home_ctr).

B12phpfw core (CRUD) code – How to get ONLY banana ?

It is not easy to see need to why convert code from procedural MVC to OOP MVC with namespaces and autoloading. For navigation (url-s, links) code procedural and OOP is same – OOP does not help. Procedural MVC user module code is more clear and readable. So why is OOP better ?

Some say: “is OOP mistake ?” – eg lack of reusability in OOP – to get banana (some method or attribute) you get also gorilla holding banana and whole gorilla`s jungle (all higher classes with complicated dependencies). It is why B12phpfw code skeleton is for CRUD modules (is not required for Mnu and Mkd modules but I put both on B12phpfw code skeleton).

Interfaces help to get ONLY banana, but coding is complicated – I could find only strong-talk-weak-work code examples about advanced use of interfaces.

  1. My not complicated interface Interf_Tbl_crud I made to standardize coding Tbl_crud model adapter classes and Db_allsites shared model class. Each simple (ee one table) module like “invoice items” module has own dir and own Tbl_crud class leading to more than 100 Tbl_crud.php model adapter classes scripts in big application (eg material and financial book keeping).
    J:\awww\www\vendor\b12phpfw\Interf_Tbl_crud.php (... hits)
    Line 20: static public function get_cursor( //instead rr
    Line 28: static public function cc(object $pp1, array $other=[]): object ; //create row
    Line 31: static public function rrnext(object $cursor ): object ; //read next from $c=get_cursor
    Line 34: static public function rrcnt( //string $sellst, //read count rows in table
    ...
  2. With DBI trait Db_allsites I eliminated two higher DB classes. So if banana (some method or attribute) is get_cursor (read) from more tables it is not in two higher DB classes which seems simplest solution but caused complicated coding in version 6. Eg invoice module works with two (or three – bill) tables : invoice and invoice_items.get_cursor banana is not in jungle (two higher DB classes) any more, gorilla and jungle is only one abstract class Config_allsites which is de facto assigning $pp1 = properties (Oracle Forms form property palete made global).
    1. Banana $pp1 = properties palette may cause difficulties in aggregate (compound, composed, multiplex) modules like Blog, Invoice… but $pp1 is inevitably (imminence, necessity) gorilla-jungle and can not be further simplified. I worked 20 years with $pp1 and globals jungle (Oracle Forms 6i) which i not so well accomplished as in B12phpfw .

    See https://phpthewrongway.com/, or Joe Armstrong why OOP sucks http://harmful.cat-v.org/software/OO_programming/why_oo_sucks.

    Similar “simplest solution” three dirs M,V,C is (I think) bad, I have opinion -:) for 3dirs lovers who put in foreground coding technic (M,V,C code separation) instead pages (functionality, business logic , eg invoice page).

During winter 2019/2020 year (much to late because I tested lot what others did) I made Version 6. of menu and CRUD PHP code skeleton (own framework named “B12phpfw”) – core code is about 50 kB.
I also made posts module “Msg” with posts edited with WYSIWYG Markdown SimpleMDE editor (or HTML WYSIWYG Sumernote editor or … any editor you wish). Msg module is based on B12phpfw, also very small code.

B12phpfw code skeleton and Msg application on B12phpfw skeleton is minimal PHP code to learn (medium/advanced knowlege) PHP !

Why ?

  1. I do not like proposed solutions in best php frameworks (Laravel, Simfony, Yii…) and learning sources (internet, books).
  2. I think that eg module invoice php code should be in own folder like Oracle Forms form invoice.fmb instead all forms/reports in 3 folders: M, V, C).
  3. I think that should be simple/fast/professional : shares (globals), routing, dispaching, classes loading , web rich text editing – it is why I spend many hours coding my B12phpfw (huge time wasting which should do software authors, not sw users-programers like me).

Compared B12phpfw Msg (blog) module and TraversyMVC blog module and Mini3 module URLs Youtube songs adresses

Why I do not like proposed solutions in existing PHP frameworks and what I did to (I hope) improve fw code. Red colored features and three asterisks *** are main reasons for B12phpfw, but I improved also other features.

TraversyMVC (has video) and Mini3 are simplified, with some (many?) differences Laravel, Simfony, Yii… B12phpfw is much more different – see red colored features.

Feature B12phpfw TraversyMVC and Mini3
1. ***Modules in own folder like Oracle Forms .fmb has – it is main reason for B12phpfw ! has not like Mini3 – all forms/reports in 3 folders: M, V, C
2. Name spaced classes (functional name spacing) has has not – no functional, no positional (paths) name spacing, Mini3 MVC PHP fw which is in my opinion better than TraversyMVC : https://github.com/panique/mini3 has name spaced classes
3. Number of folders (my opinion) optimal to many like Mini3
4. Minimal PHP code to learn (medium) PHP (my opinion) optimal (but we could add additional code) good but not enough like Mini3 eg see WYSIWYG, globals…
5. Functional methods, attr. etc naming (my opinion) good could be better
6. ***Global classes, methods etc (my opinion) good bad like Mini3
7. (Posts edited with any) WYSIWYG editor has has not like Mini3
8. ***Home_ctr or Home_mdl CRUD layer methods do not know for underlaying Db_allsites layer PDO methods, MySql, Oracle… has much improved has not like Mini3
9. OOP has has like Mini3
10. namespaces (own PSR-4 (or Composer’s) autoloading classes scripts) improved has not, Mini3 has
11. ***All scripts are included (ee no http jumps except some jumps to other module) has has not, Mini3 has, B12phpfw took it from Mini3
12. jQuery only for Bootstrap 5 yes yes, Mini3 has own CSS
13. no AJAX, no JSON yes yes, Mini3 has basic jQuery AJAX explained
14. server side validation has has like Mini3
15. authentification (log in / out) has has, Mini3 has not
16. authorization (only logged in users may execute some code ee CRUD code…) has has, Mini3 has not
17. ***Own debugging very simple and useful : msg in pre tag or popup JS msg). xdebug also helps. has has not like Mini3

6. CRUD selfjoin table forum – message board PDO SQLite

HOME   . Downloads

Excellent code to learn object oriented programming, model-view-controller code separation (structured programming), single page application (4 utility-helper functions + 5 CRUD functions), all one selfjoin table CRUD code in one script, require scripts, not URL them, most code outside Apache doc root, Javascript msg & yes-no dialogs and key TAB->ENTER.
(see Home -> site_ver2.rar).

6.Dec.2015 code refactored and parialy improved. Later (next year) I shall finish all I planed and upload also  site_ver2.rar where is code outside appl. root. Below is newest code, other is in
Home -> site_ver2.rar

2.Sept.2015 Download – see site_ver2.rar from my article 9. Code below is enough to understand – it is not much changed.

1. Download sqlitestudio and install DDL at end tema.php

2.     1_sync_tema_sifrarnik_JtoH.bat

rem SINCHRONIZATION: 2 click this .bat script (or Git)
rem J:\awww\apl\dev1\01apl\04tema\1_sync_tema_sifrarnik_JtoH.bat
rem
rem robocopy <Original Folder> <Destination Folder> /e /purge
rem **** OUTPUTS EG : *****
rem Total Copied Skipped Mismatch FAILED Extras
rem Dirs : 20 0 20 0 0 0
rem Files : 94 6 88 0 0 0
rem Bytes : 453.9 k 36.6 k 417.2 k 0 0 0
rem Times : 0:00:00 0:00:00 0:00:00 0:00:00
rem
rem Speed : 708811 Bytes/sec. 40.558 MegaBytes/min.
rem Ended : 6. prosinca 2015. 20:33:19

rem 1. resources outside appl. tree - utils, settings, css, img
rem 1.1 
robocopy J:\awww\apl\dev1\config_site.php H:\awww\apl\dev1\config_site.php /e /purge
rem 1.2
robocopy J:\awww\apl\zinc H:\awww\apl\zinc /e /purge

rem 2. inside appl. tree
robocopy J:\awww\apl\dev1\01apl\04tema H:\awww\apl\dev1\01apl\04tema /e /purge

pause

3. tema.php Code – all in one script ~350 lines :

<?php
/**
* Themes (threads) (msg-reply, task, menu) 
* Teme (niti) (poruka-odgovor, zadaća, izbornik)
* PHP, Javascript, PDO SQLite3, selfjoin, 2015.12.7
*
* LICENCE: Free code example - if you use it, do not remove this:
* Slavko Srakočić, Croatia, Zagreb
* see my blog http://phporacle.altervista.org
*
*
* 1. ADRESSES :
* J:\awww\apl\dev1\01apl\04tema\tema.php
* http://dev1:8083/01apl/04tema/tema.php
*
* 2. robocopy (or Git) SYNCHRONIZATION: 2 click this .bat script (or git)
* J:\awww\apl\dev1\01apl\04tema\1_sync_tema_sifrarnik_JtoH.bat
*
* 3. DDL: see CREATE TABLE message at this script end
*
* 4. Home page looks like :
* Open new thema (nit, thread) Help = Klik Ord.Nr
* |...| = |bytes| idniti, rbruniti, nivo, id, idviši |
* 1. MSGs self join sqlite3 THIS PG by ss @ 2015-03-25 01:41 |DELETE| 1900 |UPDATE |1,0,0 ,1,0|
*     1. funkcije by ss @ 2015-03-25 01:42 |DEL| 1654 |UPD|1,1,1 ,2,1|
*          2. Re: funkcije by ss @ 2015-03-26 14:31 |DEL| 14 |UPD|1,2,2 ,18,2|
*     3. funkcija save() by ss @ 2015-03-25 01:52 |DEL| 1296 |UPD|1,3,2 ,3,2|
* ...
* 2. Z-WAMP menu by aa @ 2015-12-05 15:58 |DELE| 14 |UPD |3,0,0 ,25,0|
*
*
* 5. PHP CREATED JAVASCRIPT MSG & YES-NO DIALOGS :
* $this->utl->phpjsmsg(7 parameters), phpjsmsgyn() 
* 6. F U N C T I O N S IN THIS SCRIPT
*LINE FUNCTION WHAT (HOW) IS DOING 
*89: public __construct() { // INITIALIZE (B O O T S T R A P I N G)
*151: public ctr() { // M A N A G E (R O U T I N G)
*192: protected crerec() { // crud1. FRONTEND A D D SELFJOIN ROW
*257: protected tbl() { // crud2. D I S P L A Y HIERARCHY
*363: protected frm_rpt() { // crud3. R O W REPORT (m s g b o d y...)
*417: protected frm_post() // crud4. ENTER USER DATA & PROCESS IT
*491: protected delrec() { // crud5 BACKEND D E L E T E R O W
*522: protected valid() {
*538: protected logAndDie(Exception $e) {
*
*/
use utlmoj\utlmoj as utl;

ini_set('max_execution_time', 0);
$start = microtime(true);

$selfj = new SelfJoin(); // should be named: MessageBoard, MainMenu...
$selfj->ctr(); //ROUTING = starts scripts according url params cmd, ... 

$end = microtime(true);

echo '<br />'.'Vrijeme izvođenja: '.($end - $start) . ' sekundi';

class SelfJoin { // should be named: MessageBoard, MainMenu...
 
 // 1. C R U D (M O D E L) PROPERTIES :
 protected $inTransaction = false;
 
 protected $db;
 protected $table;
 protected $idname;
 protected $idval;
 // c r u d actions (CURSORS = named sql set process comands):
 // D = cursor for deleting
 protected $cdel_row_byid ; 
 // R e a d cursor
 protected $cr_max_rbrdet_inm ;
 protected $cr_byid_nivo_orddet_inm ;
 protected $cr_byid ; // R E A D r o w B Y I D (M O D E L c R u d)
 protected $cr_byid_colname ; 
 // U p d a t e cursor
 protected $cu_incr_rbrdet_inm ;
 // C r e a t e cursor
 protected $cc_mast_or_det ;
 
 // 2. U T L S F U N C T I O N S (H E L P E R S) :
 protected $utl; // OUTSIDE WEBSERVERDOCROOT
 // a d r e s s properties :
 protected $curpgurl;
 protected $curpgpath;

 // 3. V I E W PROPERTIES :
 protected $form_errors = array();
 
 
 public function __construct() { // I N I T I A L I Z E (B O O T S T R A P I N G)
 set_exception_handler(array($this,'logAndDie'));
 
 // C R U D (m o d e l) properties :
 $this->table = 'message';
 $this->idname = 'id';
 $this->idval_url = isset($_REQUEST[$this->idname]) ?
 intval($_REQUEST[$this->idname]) : '';
 
 // C R U D actions (cursors, named sql set process comands):
$this->cdel_row_byid = // cd_ means cursor for deleting
"DELETE FROM $this->table WHERE $this->idname=?"; //$values[]=$parent_id;

$this->cr_max_rbrdet_inm;
"SELECT MAX(thread_pos) FROM $this->table WHERE thread_id = ? AND parent_id = ?"; 

$this->cr_byid_nivo_orddet_inm= // cr_ means cursor for reading
"SELECT thread_id,level,thread_pos FROM $this->table WHERE $this->idname=?";

$this->cu_incr_rbrdet_inm=
"UPDATE $this->table SET thread_pos = thread_pos + 1 WHERE thread_id = ? AND thread_pos >= ?";

$this->cc_mast_or_det="INSERT INTO $this->table "
. "($this->idname, thread_id,parent_id, thread_pos, posted_on, level, author, subject, body) "
.'VALUES (?,?,?,?,?,?,?,?,?)';

$this->cr_byid=
"SELECT author,subject,body,posted_on FROM $this->table WHERE $this->idname = ?";

$this->cr_byid_colname=
"SELECT subject FROM $this->table WHERE $this->idname = ?";

 
 // 1. g l o b a l u t l s, s e t t i n g s :
 $curpgpath =__FILE__; $mastpgpath=$curpgpath; //dirname(__DIR__) ;
 $this->curpgpath = $curpgpath;
 require_once($_SERVER['DOCUMENT_ROOT'].'/config_site.php');
 $this->curpgurl = $curpgurl ; 
 //htmlentities($_SERVER['PHP_SELF'],ENT_QUOTES);
 //$_SERVER['DOCUMENT_ROOT'] = eg J:\awww\apl\dev1\
 
 $this->utl = $utl;
 
?><SCRIPT LANGUAGE="JavaScript"><!-- Begin 
<?php echo file_get_contents($jsd.$ds.'key_pressed.js');?>//End --></SCRIPT><?php if ('') $utl->phpjsmsg('aaaaaaaaaa key_ pressed.js IS INCLUD E D');
 
 // 2. g l o b a l c r u d :
 $dbi = 'sqlite3'; $sqlitetbl = 'tema.sqlite3' ; //$dsn='default';
 require_once($gloresdir.$ds.'db_conn.php'); //requires klase/dbi.php
 $this->db = $db;
// ******************************************
 $title = '<h1>Teme (threads) (poruka-odgovor, zadaća, izbornik) selfjoin PHP PDO SQLite3</h1>' ;
 $title2 = 'Teme selfjoin'; // ibrowser tab txt
 $basecss=$cssd.$ds.'style00.css'; //'default' or $cssd.$ds.'style00.css';
 include ($gloresdir.'/hdr.php');
 //include ($cnfgd.$ds.'hdr.php');

 } // e n d _ _ c o n s t r u c t
 
 
 
 
 public function ctr() { // M A N A G E (R O U T I N G)
 // The value of $_REQUEST['cmd'] tells us what to do
 $cmd = isset($_REQUEST['cmd']) ? $_REQUEST['cmd'] : 'tbl';
 // frontend - user actions (backend in sqlitestudio) :
 switch ($cmd) {
 case 'delrec':
 $this->utl->phpjsmsgyn('Obrisati redak ?'
 , '?cmd=delrecyes&'.$this->idname.'='.$this->idval_url);
 break;
 case 'delrecyes': 
 //case 'delrec': 
 if ($this->idval_url) $this->delrec($this->idval_url);
 else $this->utl->phpjsmsg('Nije zadana šifra retka za brisanje !');
 $this->tbl();
 break;
 case 'frm_rpt': // read an self join r o w
 $this->frm_rpt();
 break;
 case 'frm_post': // display form to post self join r o w
 $this->frm_post();
 break;
 case 'crerec': // insert - i n s e r t posted self join r o w
 if ($this->valid()) { // if m s g e is v a l i d,
 $this->crerec(); // then i n s e r t it
 $this->tbl(); // and display self join list
 } else {
 $this->frm_post(); // otherwise, redisplay the posting form
 }
 break;
 case 'tbl': // show self join r o w s list
 default:
 $this->tbl();
 break;
 }
 }





 // F R O N T E N D - user actions :
 protected function crerec() { // crud1. FRONTEND A D D SELFJOIN ROW
 $parent_id = isset($_REQUEST['parent_id']) ?
 intval($_REQUEST['parent_id']) : 0;
 // Make sure m s g e doesn't change while we're working with it.
 $this->db->beginTransaction();
 $this->inTransaction = true;
 // is this m s g e a reply?
 if ($parent_id) {
 // get the thread, level, and thread_pos of the parent m s g e
 //"SELECT thread_id,level,thread_pos FROM $this->table WHERE $this->idname=?";
 $st = $this->db->prepare($this->cr_byid_nivo_orddet_inm);
 $st->execute(array($parent_id));
 $parent = $st->fetch();
 // a reply's level is one greater than its parent's
 $level = $parent['level'] + 1;
 // what's biggest thread_pos in this thread among m s g e s with the same parent? 
 //"SELECT MAX(thread_pos) FROM $this->table WHERE thread_id = ? AND parent_id = ?";
 $st = $this->db->prepare($this->cr_max_rbrdet_inm);
 $st->execute(array($parent['thread_id'], $parent_id));
 $thread_pos = $st->fetchColumn(0);
 // are there existing replies to this parent?
 if ($thread_pos) {
 // this thread_pos goes after the biggest existing one
 $thread_pos++;
 } else {
 // this is the first reply, so put it right after the parent
 $thread_pos = $parent['thread_pos'] + 1;
 }
 // increment thread_pos of all m s g e s in the thread that come after this one 
 //"UPDATE $this->table SET thread_pos = thread_pos + 1 WHERE thread_id = ? AND thread_pos >= ?";
 $st = $this->db->prepare($this->cu_incr_rbrdet_inm);
 $st->execute(array($parent['thread_id'], $thread_pos));
 // the new m s g e should be i n s. with the parent's thread_id
 $thread_id = $parent['thread_id'];
 } else {
 // m s g e is not reply, so it's the s t a r t of new t h r e a d
 $thread_id = $this->db->query(
 "SELECT MAX(thread_id) + 1 
 FROM $this->table")->fetchColumn(0);
 // If there are no rows yet, make sure we s t a r t at 1 for thread_id
 if (! $thread_id) {
 $thread_id = 1;
 }
 $level = 0;
 $thread_pos = 0;
 }
 // i n s e r t m s g e into DB. Using prepare() and execute() makes sure that all fields are properly quoted 
 //"INSERT INTO $this->table "
 //. "($this->idname, thread_id,parent_id, thread_pos, posted_on, level, author, subject, body) "
 //.'VALUES (?,?,?,?,?,?,?,?,?)';
 $st = $this->db->prepare($this->cc_mast_or_det);
 $st->execute(array(
null, $thread_id, $parent_id, $thread_pos, date('c'), $level
,$_REQUEST['author'], $_REQUEST['subject'],$_REQUEST['body']));
 // Commit all the operations
 $this->db->commit();
 $this->inTransaction = false;
 }
 
 
 
 
 
 
 
 protected function tbl() // crud2. D I S P L A Y H I E R A R C H Y
 { //print '<h2><a href="http://dev:8083/test/books/a01cookbook/tema.php">Teme (poruka-odgovor)</a></h2>';
 // provide a way to p o s t non-reply m s g e
 // IZBORNIK t b l - i c e :
 
 // t b l h d r r o w (action menu) :
 $lnk_addmaster = '<strong>'."<a class='btn' href='".$this->curpgurl
 ."?cmd=frm_post'>Otvoriti novu temu (nit, thread)</a>"
 .'</strong> ';
 print $lnk_addmaster;
 if ('1') print '&nbsp;&nbsp;&nbsp; Help = Klik Rbr.
 &nbsp;&nbsp;&nbsp;
 |...| = | bytes|idniti, rbruniti, nivo, id, idviši| '
 .'<hr/>';

 //order m s g s by their thread (thread_id) and their position within thread (thread_pos)
 $st = $this->db->query(
 "SELECT $this->idname, subject,author"
 .",LENGTH(body) body_length,posted_on,level,thread_id,thread_pos
 ,parent_id,url"
 ." FROM $this->table ORDER BY thread_id,thread_pos");
 
 
 while ($row = $st->fetch()) {
 
 $when = date('Y-m-d H:i', strtotime($row['posted_on']));
 
 // indent m s g e s with level > 0
 print '<font style="font-family: Courier;">'
 .str_repeat('&nbsp;',2 * $row['level']) .'</font>';

 // print info about m s g with
 // - link to open page eg http://dev1:8083/01apl/04tema/tema.php
 // - link to read it
 print
 // ----------- 1. thread_id (NIT) = ORD.NO :
 ( ($row['level'] == 0) // branch root
 ?
 //'<strong>'
 // Help (msg content report) = Klik Rbr :
 "<a href='" . $this->curpgurl
 . "?cmd=frm_rpt&amp;$this->idname={$row[$this->idname]}"
 ."'>"
 . '<span class="btnsmall">'
 .'<font style="color: black; font-family: Courier;">' 
 // background-color: red; 
 .str_repeat('&nbsp;', (6 - strlen($row['thread_id'])) )
 .$row['thread_id'] //.'</strong>'
 .'</font>'
 . '</span>'
 .'</a>'
 .'. ' 
 :
 // ----------- 2. thread_pos = ORD.NO IN THREAD - Help (msg content report) = Klik Rbr u niti :
 "<a href='" . $this->curpgurl
 . "?cmd=frm_rpt&amp;$this->idname={$row[$this->idname]}"
 ."'>"
 //.'&nbsp;&nbsp;'.$row['thread_pos'].'. ' .'</strong>'
 . '<span>'
 .'<font style="color: black; background-color: white; font-family: Courier;">' 
 .str_repeat('&nbsp;', (6 - strlen($row['thread_pos'])) )
 .$row['thread_pos'] //.'</strong>'
 .'</font>'
 . '</span>'
 .'</a>'
 .'. ' 
 
 
 )
 // Klik msg to open page :
 // eg http://dev1:8083/index.php?cmd=lsweb&dir=J:\awww\apl\dev1\test\01info
 ."<a href='" . htmlentities($row['url'],ENT_QUOTES)
 ."'>"
 . htmlentities($row['subject'],ENT_QUOTES)
 .'</strong>'
 . '</a> '
 . ' by '. htmlentities($row['author'],ENT_QUOTES) . ' @ '
 . htmlentities($when,ENT_QUOTES)
 ;
 // L I N K c m d = d e l r e c
 printf('<a href="%s?cmd=delrec&'.$this->idname.'=%s'
 //.'&'.'rrgo'.'=%s'
 .'">'.'%s</a>'
 , $this->curpgurl
 , $row[$this->idname]
 //, $pgrr1
 , '<font style="color: red;">'.' |'.'</font>'
 .'BRIŠI' // d e l e t e
 .'<font style="color: red;">'.'|'.'</font>'
 );
 print " {$row['body_length']} " // bytes|
 . " |PROMJ " // u p d a t e
 ;
if ('1') print "|{$row['thread_id']},{$row['thread_pos']},{$row['level']}
,{$row[$this->idname]},{$row['parent_id']}|";
print '<br/>';

 } // e n d p r i n t r o w s
 
 print '<hr/>'.$lnk_addmaster;
 } // e n d f n t b l ( )

 
 
 
 
 protected function frm_rpt() { // crud3. R O W REPORT (m s g b o d y...)
 // make sure the m s g e i d we are passed is an integer and really represents a m s g e 
 if (! isset($_REQUEST[$this->idname])) {
 throw new Exception('Nije formirana šifra poruke');
 }
 $id = intval($_REQUEST[$this->idname]);
 //"SELECT author,subject,body,posted_on FROM $this->table WHERE $this->idname = ?";
 $st = $this->db->prepare($this->cr_byid);
 $st->execute(array($id));
 $msg = $st->fetch();
 if (! $msg) {
 throw new Exception('Loša šifra poruke');
 }
 /* don't display user-entered HTML, but display newlines as
 HTML line breaks */
 $body = str_replace('{{/strong}}','</strong>',
 str_replace('{{strong}}','<strong>',
 nl2br(htmlentities($msg['body']))
 ));
 //
 while (false !== strpos($body, '{{url}}')) {
 $beg = strpos($body, '{{url}}');
 $end = strpos($body, '{{/url}}');
 $url = substr($body, $beg+7, $end - $beg -7);
 $url2 = '<a href="'.$url.'">'.$url.'</a>'; // </strong>
 $body = str_replace('{{url}}'.$url.'{{/url}}',$url2,$body);
 //$body .= '<br />'.$url2;
 }
 //
 // display m s g e with links to reply and return to the m s g e list
 $subject = htmlentities($msg['subject']);
 $author = htmlentities($msg['author']);
 // ---------------------------------------
 // h d r m e n u f r m _ v i e w - a
 // --------------------------------------
 print<<<_HTML_
 <h2>$subject</h2>
 <h3>
 by $author &nbsp; &nbsp;
 <a href="$this->curpgurl?cmd=frm_post&parent_id=$id">Odgovor</a>
 &nbsp; &nbsp; <a href="$this->curpgurl?cmd=tbl">Stablo poruka</a>
 </h3>
 <hr/>

 <p>$body</p>

_HTML_;
 } // tbl()

 
 
 
 
 
 protected function frm_post() // crud4. ENTER USER DATA & PROCESS IT
 {
 $safe = array();
 foreach (array('author','subject','body') as $field) {

 // escape input values :
 if (isset($_POST[$field])) {
 $safe[$field] = htmlentities($_POST[$field]);
 } else { $safe[$field] = ''; }

 // make err m s g s display in red :
 if (isset($this->form_errors[$field])) {
 $this->form_errors[$field] = '<span style="color: red">' .
 $this->form_errors[$field] . '</span><br/>';
 } else { $this->form_errors[$field] = ''; }
 } // e n d f o r e a c h


 // is this m s g e reply ?
 if (isset($_REQUEST['parent_id']) &&
 $parent_id = intval($_REQUEST['parent_id'])) {
 // send parent_id along when form is submitted
 $parent_field = sprintf(
 '<input type="hidden" 
 name="parent_id" 
 value="$this->idname" />'
 , $parent_id);
 // if no subject's been passed in, use parent's subject
 if (! strlen($safe['subject'])) {
 //"SELECT subject FROM $this->table WHERE $this->idname = ?";
 $st = $this->db->prepare($this->cr_byid_colname);
 $st->execute(array($parent_id));
 $parent_subject = $st->fetchColumn(0);
 /* prefix 'Re: ' to parent subject if it exists and
 doesn't already have 'Re:' */
 $safe['subject'] = htmlentities($parent_subject);
 if ( $parent_subject
 && (! preg_match('/^re:/i',$parent_subject)))
 { $safe['subject'] = "Re: {$safe['subject']}"; }
 }
 } else { $parent_field = ''; }


 // display posting form, with errors and default values
 print<<<_HTML_
 <form method="post" action="$this->curpgurl">
 <table>
 <tr>
 <td>Your Name:</td>
 <td>{$this->form_errors['author']}
 <input type="text" name="author" value="{$safe['author']}" />
 </td>
 <tr>
 <td>Subject:</td>
 <td>{$this->form_errors['subject']}
 <input type="text" name="subject" value="{$safe['subject']}" />
 </td>
 <tr>
 <td>Poruka:</td>
 <td>{$this->form_errors['body']}
 <textarea rows="4" cols="30" wrap="physical"
 name="body">{$safe['body']}</textarea>
 </td>
 <tr><td colspan="2"><input type="submit" value="Pošaljite poruku" /></td></tr>
 </table>
 $parent_field
 <input type="hidden" name="cmd" value="crerec" />
 </form>
_HTML_;
}



 // B A C K E N D - administrator actions :
 protected function delrec($parent_id) { // crud5 BACKEND D ELETE R OW
 //$parent_id = isset($_REQUEST[$this->idname]) ? intval($_REQUEST[$this->idname]) : 0;
// $this->idval_url
//basename(__FILE__).' SAYS'.'<br>'.'$id'.'=='.$parent_id.'=='
if ('') $this->utl->phpjsmsg('***** '.__FUNCTION__.'() SAYS: ' 
 .'<br>'.'$dml=***'.$this->cdel_row_byid.'***<br>'.'?=$parent_id=***'
 .$parent_id.'***');
 // is this m s g e a reply?
 if ($parent_id) {
 // Make sure m s g e doesn't change while we're working with it.
 $this->db->beginTransaction();
 $this->inTransaction = true;
 //"DELETE FROM $this->table WHERE $this->idname=?"; //$values[]=$parent_id;
 $st = $this->db->prepare($this->cdel_row_byid);
 //or $this->db->get_con()->prepare ?
 $st->execute(array($parent_id));
 // fetchAll() is needed only for s e l e c t 
 //Commit all the operations
 $this->db->commit();
 $this->inTransaction = false;
 } 
 }

 
 
 
 



 // 5. makes sure something is entered in each field :
 protected function valid() {
 $this->form_errors = array();
 // R E Q U I R E D U S E R D A T A :
 if (! (isset($_POST['author']) && strlen(trim($_POST['author'])))) {
 $this->form_errors['author'] = 'Upišite vaše ime (autor).';
 }
 if (! (isset($_POST['subject']) && strlen(trim($_POST['subject'])))) {
 $this->form_errors['subject'] = 'Upišite naslov poruke.';
 }
 if (! (isset($_POST['body']) && strlen(trim($_POST['body'])))) {
 $this->form_errors['body'] = 'Upišite tekst poruke.';
 }
 return (count($this->form_errors) == 0);
 }

 // 6.
 protected function logAndDie(Exception $e) {
 print 'ERROR: ' . htmlentities($e->getMessage());
 if ($this->db && $this->db->inTransaction()) {
 $this->db->rollback();
 }
 exit();
 }
 
 
 
 
 
} // e n d c l a s s



// <strong><a href="http://...">http://...</a></strong>
// if (false !== strpos($string, $substring)) { /* found it! */ }
// if (strpos($haystack, $needle) !== false) echo 'match!';
// $withoutCommas = is_numeric(str_replace(',', '', $number));

/*
ALTER TABLE message RENAME TO sqlitestudio_temp_table;

CREATE TABLE message (
 id INTEGER PRIMARY KEY AUTOINCREMENT,
 subject CHAR (255),
 url VARCHAR (500),
 thread_id [INT UNSIGNED] NOT NULL,
 thread_pos [INT UNSIGNED] NOT NULL,
 level [INT UNSIGNED] NOT NULL,
 parent_id [INT UNSIGNED] NOT NULL,
 author CHAR (255),
 body MEDIUMTEXT,
 posted_on DATETIME NOT NULL
);

INSERT INTO message (
id
,subject
,url
,thread_id
,thread_pos
,level
,parent_id
,author
,body
,posted_on
)
SELECT
id
,subject
,url
,thread_id
,thread_pos
,level
,parent_id
,author
,body
,posted_on
FROM sqlitestudio_temp_table;

DROP TABLE sqlitestudio_temp_table;




 TO INCLUDE RESULTS OF EXECUTING FN OR EXPRESSION WITHIN A STRING
 You can put vars, obj.prop, array el. (if subscript is unquoted) directly in double-quoted strings:

 eg print "You owe $amounts[payment] immediately.";
 eg print "My circle's diameter is $circle->diameter inches.";

 Curly braces around more complicated expressions to interpolate them into a string:

 print "I have {$children} children.";
 print "You owe {$amounts['payment']} immediately.";
 print "My circle's diameter is {$circle->getDiameter()} inches.";

 Direct interpolation or string concat. also works with heredocs:
 print <<< END
 Right now, the time is
 END
 . strftime('%c') . <<< END
 but tomorrow it will be
END
 . strftime('%c',time() + 86400);

 
 {{url}} http://dev1:8083/my_dev/pdo/tema/tema.php {{/url}}
 J:\awww\apl\dev1\my_dev\pdo\tema\tema.php
 J:\dev_web\htdocs\test\books\a01cookbook\tema.php


Promjene podataka programom J:\aplp\aplp\sqlitestudio\SQLiteStudio.exe

SELFJOIN TABLE :
 1. C INSERT frm data,
 2. R DISPLAY tbl, row,
 3. V VALIDATE, E set_exception_handler
Not neccessarily here:
U UPDATE and D DELETE WITH J:\aplp\aplp\sqlitestudio\SQLiteStudio.exe

*/

5. CRUD simple table (ID,…some data) PDO SQLite

HOME  2.Sept.2015 Download – see site_ver2.rar from my article 9.

Excellent code to learn object oriented programming, model-view-controller code separation (structured programming), single page application (require scripts, not URL them), most code outside Apache doc root.

1. Download sqlitestudio and install DDL below.

--*****************************************
-- sqlite 3
--*****************************************
/*
SELECT * FROM message ;
select * from zodiac ;
*/

/*
http://dev:8083/test/books/a01cookbook/tema.php
J:\dev_web\htdocs\test\books\a01cookbook\tema.php

sqlite db is: J:\dev_web\htdocs\test\books\a01cookbook\tema.sqlite
                                         
SELFJOIN TABLE :
   1. C INSERT frm data, 
   2. R DISPLAY tbl, row, 
   3. V VALIDATE, E set_exception_handler 
Not neccessarily here:
U UPDATE and D DELETE WITH J:\aplp\aplp\sqlitestudio\SQLiteStudio.exe

TEME (msgs-PORUKE I replays-ODGOVORI) SELFJOIN
TEME (msgs-PORUKE I replays-ODGOVORI) SELFJOIN
----------------------------------------------------------------
5 KEYS: id,thread_id,parent_id,level,thread_pos
----------------------------------------------------------------
1 1 0 0 0   TEMA1 (thread1) by ss @ 2015-03-25 00:41 (99 bytes)
2 1 1 1 1     funkcije by ss @ 2015-03-25 00:42 (242 bytes)
3 1 2 2 2       funkcija save() by ss @ 2015-03-25 00:52 (1335 bytes)
6 1 2 2 3       funkcija frm_post() by ss @ 2015-03-25 19:29 (303 bytes) 

4 2 0 0 0   TEMA2 CRUD šifrarnika sqlite3 by ss ...

5 3 0 0 0   TEMA3 MAPE web servera by ss ...
______________________________________________________________
Otvoriti novu temu (nit, thread)


*/

-- sqlite db: J:\dev_web\htdocs\test\books\a01cookbook\tema.sqlite
CREATE TABLE message (
  id          INTEGER PRIMARY KEY AUTOINCREMENT,
  posted_on   DATETIME NOT NULL,
  author      CHAR(255),
  subject     CHAR(255),
  body        MEDIUMTEXT,
  thread_id   INT UNSIGNED NOT NULL,
  parent_id   INT UNSIGNED NOT NULL,
  level INT   UNSIGNED     NOT NULL,
  thread_pos  INT UNSIGNED NOT NULL
);



--sqlite db: J:\dev_web\htdocs\test\books\a01cookbook\zodiac.sqlite
CREATE TABLE zodiac (
  id          INT UNSIGNED NOT NULL,
  sign        CHAR(11),
  symbol      CHAR(13),
  planet      CHAR(7),
  element     CHAR(5),
  start_month TINYINT,
  start_day   TINYINT,
  end_month   TINYINT,
  end_day     TINYINT,
  PRIMARY KEY(id)
)
;

INSERT INTO zodiac VALUES (1,'Aries','Ram','Mars','fire',3,21,4,19);
INSERT INTO zodiac VALUES (2,'Taurus','Bull','Venus','earth',4,20,5,20);
INSERT INTO zodiac VALUES (3,'Gemini','Twins','Mercury','air',5,21,6,21);
INSERT INTO zodiac VALUES (4,'Cancer','Crab','Moon','water',6,22,7,22);
INSERT INTO zodiac VALUES (5,'Leo','Lion','Sun','fire',7,23,8,22);
INSERT INTO zodiac VALUES (6,'Virgo','Virgin','Mercury','earth',8,23,9,22);
INSERT INTO zodiac VALUES (7,'Libra','Scales','Venus','air',9,23,10,23);
INSERT INTO zodiac VALUES (8,'Scorpio','Scorpion','Mars','water',10,24,11,21);
INSERT INTO zodiac VALUES (9,'Sagittarius','Archer','Jupiter','fire',11,22,12,21);
INSERT INTO zodiac VALUES (10,'Capricorn','Goat','Saturn','earth',12,22,1,19);
INSERT INTO zodiac VALUES (11,'Aquarius','Water Carrier','Uranus','air',1,20,2,18);
INSERT INTO zodiac VALUES (12,'Pisces','Fishes','Neptune','water',2,19,3,20);

2. Controller and view ~ 120 lines :

<?php
// http://dev:8083/test/books/a01cookbook/
// J:\dev_web\htdocs\test\books\a01cookbook\zodiac.php

    // ***********************************
    // 1. M O D E L - file where b u i l d_ q u e r y ( )  is defined
    // ***********************************
$fields = array(
   'sign'
 , 'symbol'
 , 'planet'
 , 'element'
 , 'start_month'
 , 'start_day'
 , 'end_month'
 , 'end_day'
);
$lbls = array(
    'Znak'
  , 'Simbol'
  , 'Planeta'
  , 'Element'
  , 'Od mjeseca'
  , 'Od dana'
  , 'Do mjeseca'
  , 'Do dana'
);

include __DIR__ . '/mdl.php';
$db = new PDO('sqlite:zodiac.sqlite');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);


    // ***********************************
    // 2. C O N T R O L L E R
    // ***********************************
$cmd = isset($_REQUEST['cmd']) ? $_REQUEST['cmd'] : 'show';
switch ($cmd) 
{
   case 'edit':
       try {
          $st = $db->prepare('SELECT ' . implode(',',$fields) .
                             ' FROM zodiac WHERE id = ?');
          $st->execute(array($_GET['id']));
          $row = $st->fetch(PDO::FETCH_ASSOC);
       } catch (Exception $e) {
           $row = array();
       }
   case 'add':
           print '<form method="post" action="' .
                 htmlentities($_SERVER['PHP_SELF']) . '">';
           print '<input type="hidden" name="cmd" value="save">';
           
           print '<table>';
           
           if ('edit' == $cmd) {
               printf('<input type="hidden" name="id" value="%d">',
                      $_GET['id']);
           }
           foreach ($fields as $field) {
               if ('edit' == $cmd) {
                  $value = htmlentities($row[$field]);
               } else {
                   $value = '';
               }
               printf('<tr><td>%s: </td><td><input type="text" name="%s" value="%s">',
                      $field, $field, $value);
               printf('</td></tr>');
           }
           print '<tr>
                <td></td>
                <td><input type="submit" value="Save"></td>
              </tr>';
           
           print '</table></form>';
           break;
   case 'save':
           try {
             $st = build_query($db,'id',$fields,'zodiac');
             print 'Added info.';
           } catch (Exception $e) {
             print "Couldn't add info: " . htmlentities($e->getMessage());
           }
           print '<hr>';
   case 'show':
          // ***********************************
          // 3. V I E W - P A G I N A T O R
          // ***********************************
       default:
           $self = htmlentities($_SERVER['PHP_SELF']);
           
           /* without P A G I N A T O R :
           foreach ($db->query('SELECT id,sign FROM zodiac') as $row) {
               printf('<li> <a href="%s?cmd=edit&id=%s">%s</a>',
                      $self,$row['id'],htmlentities($row['sign']));
           }
           */
      $offset = isset($_GET['offset']) ? intval($_GET['offset']) : 1;
      if (! $offset) { $offset = 1; }
      $per_page = 10;
      $total = $db->query('SELECT COUNT(*) FROM zodiac')->fetchColumn(0);
      $limitedSQL = 'SELECT * FROM zodiac ORDER BY id ' .
                    "LIMIT $per_page OFFSET " . ($offset-1);
      $lastRowNumber = $offset - 1;

           print '<a href="'.$self.'?cmd=add">Dodati redak</a><hr />';
           print '<ol>';
      foreach ($db->query($limitedSQL) as $row) {
          $lastRowNumber++;
          //print "{$row['sign']}"
          
          printf('<li> <a href="%s?cmd=edit&id=%s">%s</a>',
                      $self,$row['id'],htmlentities($row['sign']));
          echo ", {$row['symbol']} ({$row['id']}) <br/>\n";
      }
          print '</ol>';
      //
      indexed_links($total,$offset,$per_page);
      print "<br/>";
      print "(Prikazani retci $offset - $lastRowNumber od $total)";
           //
           break;
} // e n d 
 s w i t c h

3. Model- universal code ~ 70 lines :

 <?php
function build_query($db, $key_field, $fields, $table) {
    $values = array();
    if (! empty($_POST[$key_field])) {
        $update_fields = array();
        foreach ($fields as $field) {
            $update_fields[] = "$field = ?";
            // Assume data is coming from a form
            $values[] = $_POST[$field];
        }
        // Add the key field's value to the $values array
        $values[] = $_POST[$key_field];
        $st = $db->prepare("UPDATE $table SET " .
                   implode(',', $update_fields) .
                   "WHERE $key_field = ?");
    } else {
        // Start values off with a unique ID
        // If your DB is set to generate this value, use NULL instead
        $values[] = md5(uniqid());
        $placeholders = array('?');
        foreach ($fields as $field) {
            // One placeholder per field
            $placeholders[] = '?';
            // Assume the data is coming from a form
            $values[] = $_POST[$field];
        }
        $st = $db->prepare(
           "INSERT INTO $table ($key_field," .
              implode(',',$fields) . ') VALUES ('.
              implode(',',$placeholders) .')');
    }
    $st->execute($values);
    return $st;
}

function print_link($inactive,$text,$offset='') {
    if ($inactive) {
        print "<span class='inactive'>$text</span>";
    } else {
        print "<span class='active'>".
              "<a href='" . htmlentities($_SERVER['PHP_SELF']) .
              "?offset=$offset'>$text</a></span>";
    }
}

function indexed_links($total,$offset,$per_page) {
    $separator = ' | ';
    // 
    print_link($offset == 1, '<< Preth', max(1, $offset - $per_page));
    // print all groupings except last one
    for ($start = 1, $end = $per_page;
         $end < $total;
         $start += $per_page, $end += $per_page) {
             print $separator;
             print_link($offset == $start, "$start-$end", $start);
    }
    /* print the last grouping -
     * at this point, $start points to the element at the beginning
     * of the last grouping
     */
    /* the text should only contain a range if there's more than
     * one element on the last page. For example, the last grouping
     * of 11 elements with 5 per page should just say "11", not "11-11"
     */
    $end = ($total > $start) ? "-$total" : '';
    print $separator;
    print_link($offset == $start, "$start$end", $start);
    // 
    print $separator;
    print_link($offset == $start, 'Sljed >>',$offset + $per_page);
}

 

4. Multiple files upload OOP, namespaces & How to recognize mobile device – OOP, SPA, MVC domain style, PHP outside web doc root

HOME

Two scripts for multiple files upload – OOP, namespaces (see also article 10)

<?php
// 1. J:\zwamp64\vdrive\web\papl1\upload\index.php
use Classes\File\Upload; // = dir/subdir/classname 
                         // = namespace_name/clsname

// set the maximum upload size in bytes
$max = 10000 *      // 6 GB
        600 * 1024; // 600 KB
if (isset($_POST['upload'])) {
    // path to the upload folder : 
    //work: $destination = 'C:/upload_test/'; //or  __DIR__;
    $destination = DROOTPATH.DS.'FILE_TRANSFER'.DS; 
              // CONVENTION: below web doc.root
    require_once __DIR__.'/Classes/File/Upload.php'; // clsscript
    // -----------------------
    
    
    try {
        $loader = new Upload($destination);
        $loader->setMaxSize($max);
        $loader->allowAllTypes();
        $loader->upload();
        $result = $loader->getMessages();
    } catch (Exception $e) {
        echo $e->getMessage();
    }
}
?>
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Multiple Upload</title>
</head>

<body>
<?php
if (isset($result)) {
    echo '<ul>';
    foreach ($result as $message) {
        echo "<li>$message</li>";
    }
    echo '</ul>';
}
?>
<form action="" method="post" enctype="multipart/form-data">
  <p>
    <label for="image">Upload files (multiple selections permitted):
    </label>
    <input type="file" name="image[]" id="imageid" multiple>
  </p>
  <p>
    <input type="submit" name="upload" id="uploadid" value="Upload">
  </p>
</form>


<h3>Upload info ($_FILES)</h3>
<pre>
<?php if (isset($_POST['upload'])) 
{
  print_r($_FILES);
?>
Error levels in $_FILES array $err = $_FILES['file']['error'];
Error level Meaning
0 Upload successful $err = UPLOAD_ERR_OK
1 File exceeds maximum upload size specified in php.ini (default 2 MB)
  UPLOAD_ERR_INI_SIZE
  upload_max_filesize in php.ini eg 6400M (=6,4 GB)
  post_max_size = 64000M
  file_uploads = On  (see Local value in phpinfo -> Core section)
  upload_tmp_dir = "j:/wamp/tmp"  or whatever, default C:\Windows\Temp
  max_file_uploads = 20  for single request
  max_execution_time  60 sekundi<br />
  max_input_time  60 sek. (parse $_POST, $_GET, $_FILE arrays)
2 File exceeds size specified by MAX_FILE_SIZE in HTML form eg 6400M
  UPLOAD_ERR_FORM_SIZE
3 File only partially uploaded UPLOAD_ERR_PARTIAL
4 Form submitted with no file specified UPLOAD_ERR_NO_FILE
Error level 5 is currently not defined.
6 No temporary folder UPLOAD_ERR_NO_TMP_DIR (no destination_path)
7 Cannot write file to disk UPLOAD_ERR_CANT_WRITE (chmod 777)
8 Upload stopped by an unspecified PHP extension UPLOAD_ERR_EXTENSION
<?php } ?>
</pre>


<h3>$_POST parameters of this page</h3>

<table>
    <tr>
        <th>Parameter name</th>
        <th>Value</th>
    </tr>
    <?php 
     $count = 0; foreach ($_POST as $name => $value) { $count++ ?>
      <tr class="<?php echo $count % 2 == 0 ? 'alt' : ''; ?>">
        <td><?php echo htmlentities(stripslashes($name)) ?></td>
        <td><?php echo nl2br(htmlentities(stripslashes($value))) ?></td>
      </tr>
    <?php } 
echo '</table>';
?>

</body>
</html>
<?php
// 2. J:\zwamp64\vdrive\web\papl1\upload\Classes\File\Upload.php
namespace Classes\File; // dir/subdir
class Upload {

    protected $uploaded = [];
    protected $destination;
    protected $max = 51200;
    protected $messages = [];
    protected $permitted = [
        'image/gif',
        'image/jpeg',
        'image/pjpeg',
        'image/png'
    ];
    protected $typeCheckingOn = true;
    protected $notTrusted = ['bin', 'cgi', 'exe', 'js', 'pl', 'php'
           , 'py', 'sh'];
    protected $suffix = '.upload';
    protected $newName;
    protected $renameDuplicates;


    public function __construct($path) {
        if (!is_dir($path) || !is_writable($path)) {
            throw new \Exception("$path must be a valid
                 , writable directory.");
        }
        $this->destination = $path;
    }

    public function upload($renameDuplicates = true) {
        $this->renameDuplicates = $renameDuplicates;
        $uploaded = current($_FILES);
        if (is_array($uploaded['name'])) {
            // deal with multiple uploads
            foreach ($uploaded['name'] as $key => $value) {
                $currentFile['name'] = $uploaded['name'][$key];
                $currentFile['type'] = $uploaded['type'][$key];
                $currentFile['tmp_name'] = $uploaded['tmp_name'][$key];
                $currentFile['error'] = $uploaded['error'][$key];
                $currentFile['size'] = $uploaded['size'][$key];
                if ($this->checkFile($currentFile)) {
                    $this->moveFile($currentFile);
                }
            }
        } else {
            if ($this->checkFile($uploaded)) {
                $this->moveFile($uploaded);
            }
        }
    }

    public function getMessages() {
        return $this->messages;
    }

    public function getMaxSize() {
        return number_format($this->max/1024, 1) . ' KB';
    }

    public function setMaxSize($num) {
        if (is_numeric($num) && $num > 0) {
            $this->max = (int) $num;
        }
    }

    public function allowAllTypes($suffix = true) {
        $this->typeCheckingOn = false;
        if (!$suffix) {
            $this->suffix = '';  // empty string
        }
    }

    protected function checkFile($file) {
        $accept = true;
        if ($file['error'] != 0) {
            $this->getErrorMessage($file);
            // stop checking if no file submitted
            if ($file['error'] == 4) {
                return false;
            } else {
                $accept = false;
            }
        }
        if (!$this->checkSize($file)) {
            $accept = false;
        }
        if ($this->typeCheckingOn) {
            if (!$this->checkType($file)) {
                $accept = false;
            }
        }
        if ($accept) {
            $this->checkName($file);
        }
        return $accept;
    }

    protected function getErrorMessage($file) {
        switch($file['error']) {
            case 1:
            case 2:
                $this->messages[] = $file['name'] . ' is too big: (max: ' .
                    $this->getMaxSize() . ').';
                break;
            case 3:
                $this->messages[] = $file['name'] 
                     . ' was only partially uploaded.';
                break;
            case 4:
                $this->messages[] = 'No file submitted.';
                break;
            default:
                $this->messages[] = 
                   'Sorry, there was a problem uploading ' 
                   . $file['name'];
                break;
        }
    }

    protected function checkSize($file) {
        if ($file['error'] == 1 || $file['error'] == 2) {
            return false;
        } elseif ($file['size'] == 0) {
            $this->messages[] = $file['name'] . ' is an empty file.';
            return false;
        } elseif ($file['size'] > $this->max) {
            $this->messages[] = $file['name'] 
                     . ' exceeds the maximum size
                for a file (' . $this->getMaxSize() . ').';
            return false;
        } else {
            return true;
        }
    }

    protected function checkType($file) {
        if (in_array($file['type'], $this->permitted)) {
            return true;
        } else {
            if (!empty($file['type'])) {
                $this->messages[] = $file['name'] 
                    . ' is not permitted type of file.';
            }
            return false;
        }
    }

    protected function checkName($file) {
        $this->newName = null;
        $nospaces = str_replace(' ', '_', $file['name']);
        if ($nospaces != $file['name']) {
            $this->newName = $nospaces;
        }
        $extension = pathinfo($nospaces, PATHINFO_EXTENSION);
        if (!$this->typeCheckingOn && !empty($this->suffix)) {
            if (in_array($extension
                  , $this->notTrusted) || empty($extension)) {
                $this->newName = $nospaces . $this->suffix;
            }
        }
        if ($this->renameDuplicates) {
            $name = isset($this->newName) 
                       ? $this->newName : $file['name'];
            $existing = scandir($this->destination);
            if (in_array($name, $existing)) {
                // rename file
                $basename = pathinfo($name, PATHINFO_FILENAME);
                $extension = pathinfo($name, PATHINFO_EXTENSION);
                $i = 1;
                do {
                    $this->newName = $basename . '_' . $i++;
                    if (!empty($extension)) {
                        $this->newName .= ".$extension";
                    }
                } while (in_array($this->newName, $existing));
            }
        }
    }

    protected function moveFile($file) {
        $filename = isset($this->newName) 
                ? $this->newName : $file['name'];
        $success = move_uploaded_file($file['tmp_name']
              , $this->destination . $filename);
        if ($success) {
            $result = $file['name'] . ' was uploaded successfully to '
              . 'server directory : <br />'. $this->destination ;
            if (!is_null($this->newName)) {
                $result .= ', and was renamed ' . $this->newName;
            }
            $this->messages[] = $result;
        } else {
            $this->messages[] = 'Could not upload ' . $file['name'];
        }
    }
}

How to recognize mobile device – non OOP code

Excellent code to learn object oriented programming, model-view-controller code separation (structured programming), single page application (require scripts, not URL them), most code outside Apache doc root.

function findDevice() {
    $userAgent=strtolower($_SERVER['HTTP_USER_AGENT']);
$device=array('iphone','ipad','android','silk','blackberry', 'touch');
$deviceLength=count($device);

for($ii=0;$ii < $deviceLength;$ii ++) {
if(strstr($userAgent, $device[$ii])) {
return $device[$ii];
} else return 'desktop/laptop';
}
}

OOP, SPA, MVC domain style, PHP outside web doc root

Display ibrowser properties (view class methode):

  1. Device = desktop/laptop
  2. Browser = firefox
  3. userAgent = mozilla/5.0 (windows nt 6.3; win64; x64; rv:25.3) gecko/20150323 firefox/31.9 palemoon/25.3.1

Or display one property – returned from getter: Device = desktop/laptop

About OOP programs

  1. CODE INSIDE APACHE DOC ROOT (this page script) :  kod  edit  phpinfo
    INCLUDED CODE FROM OUTSIDE APACHE DOC ROOT :
  2. code behind this page script (model .pcls) :  kod  edit  phpinfo
  3. code config – set up :  kod  edit  phpinfo
  4. code helper (util) :  kod  edit  phpinfo
  5. /**
    * This page URL $idxurl = 
    *    http://dev:8083/inc/utl/get_ibrowser_device.php
    *   displays o u t p u t  o f  server script 
    *          $idx.DS.$idxscript =
    *   J:\dev_web\htdocs\inc\utl\get_ibrowser_device.php, 
    *           which contains :
    *      MODEL_fn_call from public fn __ c o n s t r u c t ( )
    *      protected_VIEW_fn_call from public fn
    *      CONTROLLER_code before c l a s s  C l i e n t
    *
    * server script i n c l u d e s scripts which are outside Apache doc root :
    *   1. config $confglob                : require_once('J:\dev_web\inc\confglob.php');
    *   2. helper (util) $edrun            : require_once('J:\dev_web\inc\utl\kod_edit_run.php');
    *   3. code behind (class) $codebehind : require_once('J:\dev_web\inc\utl\get_ibrowser_device.pcls');
    *
    * $confglob contains  P H P  s e t  u p :
    *    ini_set('display_errors','2');
    *    ERROR_REPORTING(E_ALL);
    **/

SCRIPTS:

  1. J:\dev_web\htdocs\inc\utl\get_ibrowser_device.php
    // 1. a d r e s s e s :
    if (!defined('DS')) define('DS',DIRECTORY_SEPARATOR);
    if (!defined('CONFGLOB_DIR')) define('CONFGLOB_DIR',
    realpath($_SERVER['DOCUMENT_ROOT'].'/../inc'));
    $apl = dirname(dirname(__DIR__)).DS.’htdocs’; // *** !!! *** ONLY YOU TO SET UP, used for link
    $idx = __DIR__ ; $idxscript = basename(__FILE__) ;// 2. i n c l u d e s :
    $confglob   = CONFGLOB_DIR.DS.’confglob.php’;
    $edrun      = CONFGLOB_DIR.DS.’utl’.DS.’kod_edit_run.php’;
    $codebehind = CONFGLOB_DIR.DS.’utl’.DS.’get_ibrowser_device.pcls’;
    require_once($confglob);   // c o n f i g
    require_once($edrun);      // h e l p e r
    require_once($codebehind); // m o d e l// 3. c o n t r o l l e r :
    $trigger = new Client(); // trigger becouse can direct output view// ************** e n d  c o n t r o l l e r  p r o g r a m// v i e w :
    ?>
    <!DOCTYPE html>
    <html lang=”hr”>
    <head>
    <title>Is mobile</title>
    <meta content=”text/html; charset=utf-8″; http-equiv=”content-type”>
    <!–base href=’/’–>
    <link rel=’stylesheet’ href=’lib/bootstrap/dist/css/bootstrap.min.css’ />
    <link rel=’stylesheet’ href=’src/bootstrap.min.css’ />
    </head>
    <body><h2>How to recognize mobile device – non OOP code</h2>
    <p><code><code><span style=”color:#000000; “><span style=”color:#007700; “>function </span><span style=”color:#0000BB; “>findDevice</span><span style=”color:#007700; “>() {<br>
    </span></span><code><span style=”color:#000000; “><span style=”color:#007700; “>    $</span><span style=”color:#0000BB; “>userAgent</span><span style=”color:#007700; “>=</span><span style=”color:#0000BB; “>strtolower</span><span style=”color:#007700; “>(</span><span style=”color:#0000BB; “>$_SERVER</span><span style=”color:#007700; “>[</span><span style=”color:#DD0000; “>’HTTP_USER_AGENT'</span><span style=”color:#007700; “>]);<br>
    </span><span style=”color:#FF8000; “>
    </span><span style=”color:#007700; “>$</span><span style=”color:#0000BB; “>device</span><span style=”color:#007700; “>=array(</span><span style=”color:#DD0000; “>’iphone'</span><span style=”color:#007700; “>,</span><span style=”color:#DD0000; “>’ipad'</span><span style=”color:#007700; “>,</span><span style=”color:#DD0000; “>’android'</span><span style=”color:#007700; “>,</span><span style=”color:#DD0000; “>’silk'</span><span style=”color:#007700; “>,</span><span style=”color:#DD0000; “>’blackberry'</span><span style=”color:#007700; “>, </span><span style=”color:#DD0000; “>’touch'</span><span style=”color:#007700; “>);<br>$</span><span style=”color:#0000BB; “>deviceLength</span><span style=”color:#007700; “>=</span><span style=”color:#0000BB; “>count</span><span style=”color:#007700; “>($</span><span style=”color:#0000BB; “>device</span><span style=”color:#007700; “>);<br>
    </span></span></code><span style=”color:#000000; “><span style=”color:#007700; “><br>
    for(</span><span style=”color:#0000BB; “>$ii</span><span style=”color:#007700; “>=</span><span style=”color:#0000BB; “>0</span><span style=”color:#007700; “>;</span><span style=”color:#0000BB; “>$ii </span><span style=”color:#007700; “>&lt; $</span><span style=”color:#0000BB; “>deviceLength</span><span style=”color:#007700; “>;</span><span style=”color:#0000BB; “>$ii </span><span style=”color:#007700; “>++) {<br>
    if(</span><span style=”color:#0000BB; “>strstr</span><span style=”color:#007700; “>($</span><span style=”color:#0000BB; “>userAgent</span><span style=”color:#007700; “>, $</span><span style=”color:#0000BB; “>device</span><span style=”color:#007700; “>[</span><span style=”color:#0000BB; “>$ii</span><span style=”color:#007700; “>])) {<br>
    </span><span style=”color:#FF8000; “>        </span><span style=”color:#007700; “>return $</span><span style=”color:#0000BB; “>device</span><span style=”color:#007700; “>[</span><span style=”color:#0000BB; “>$ii</span><span style=”color:#007700; “>];<br>
    } else return </span><span style=”color:#DD0000; “>’desktop/laptop'</span><span style=”color:#007700; “>;<br>
    }<br>
    }</span></span></code></code></p>
    <h2>OOP, SPA, MVC domain style, PHP outside web doc root</h2>
    <p>Display ibrowser properties (view class methode):<br />
    <?php $trigger -> out_vew_ibrowse_params() ; ?>
    </p>

    <p>Or display one property – returned from getter: Device =
    <?php echo $trigger->get_device(); ?> </p>

    <h2>About OOP programs</h2>
    <ol>
    <li>CODE INSIDE APACHE DOC ROOT (this page script) :
    <?php kod_edit_run(
    $idx       // script_dir
    , $idxscript // script
    , MDURL); ?>
    <br>
    <br>
    INCLUDED CODE FROM OUTSIDE APACHE DOC ROOT : <br>
    <li>code behind this page script (model .pcls) :
    <?php kod_edit_run(
    dirname($codebehind)  // script_dir
    , basename($codebehind) // script
    , MDURL); ?>
    <li> code config – set up :
    <?php kod_edit_run(
    dirname($confglob)  // script_dir
    , basename($confglob) // script
    , MDURL); ?>
    <li>code helper (util) :
    <?php kod_edit_run(
    dirname($edrun)  // script_dir
    , basename($edrun) // script
    , MDURL); ?>
    <li> <pre>/**
    * This page URL $idxurl = <a href=”<?php echo $idxurl; ?>”><?php echo $idxurl; ?><a>
    *   displays o u t p u t  o f  server script $idx.DS.$idxscript =
    *   <?php echo $idx.DS.$idxscript; ?>, which contains :
    *      MODEL_fn_call from public fn __ c o n s t r u c t ( )
    *      protected_VIEW_fn_call from public fn
    *      CONTROLLER_code before c l a s s  C l i e n t
    *
    * server script i n c l u d e s scripts which are outside Apache doc root :
    *   1. config $confglob                : require_once(‘<?php echo $confglob; ?>’);
    *   2. helper (util) $edrun            : require_once(‘<?php echo $edrun; ?>’);
    *   3. code behind (class) $codebehind : require_once(‘<?php echo $codebehind; ?>’);
    *
    * $confglob contains  P H P  s e t  u p :
    *    ini_set(‘display_errors’,’2′);
    *    ERROR_REPORTING(E_ALL);
    **/ </pre>
    </ol>

    <?php
    class Client
    {
    private $IbrowserProp;

    // M O D E L :
    public function __construct()   {
    $this->IbrowserProp = new getIbrowserProp();
    } // e n d  p u b l i c  f n  _ _c o n s t r u c t ( )

    public function get_device() {
    return  $this->IbrowserProp->findDevice() ;
    } // e n d

    // V I E W :
    public function out_vew_ibrowse_params() {
    $this->vew_ibrowse_params();
    } // e n d
    protected function vew_ibrowse_params()  // public private protected
    {
    echo ‘<ol>’;
    echo ‘<li>Device = ‘    . $this->IbrowserProp->findDevice() . ‘<br/>’;
    echo ‘<li>Browser = ‘   . $this->IbrowserProp->findBrowser() . ‘<br/>’;
    echo ‘<li>userAgent = ‘ . $this->IbrowserProp->getUserAgent() . ‘<br/>’;
    echo ‘</ol>’;
    } // e n d  p u b l i c  f n  _ _c o n s t r u c t ( )

    } // e n d  c l a s s  C l i e n t

    ?>
    </body>
    </html>

  2. J:\dev_web\inc\utl\get_ibrowser_device.pcls
    <?php
    ini_set("display_errors","2");
    ERROR_REPORTING(E_ALL);
    //User agent as property of object
    class getIbrowserProp
    {
    private $userAgent;
    private $device;
    private $browser;
    private $deviceLength;
    private $browserLength;
    public function __construct()
    {
    $this->userAgent=strtolower($_SERVER[‘HTTP_USER_AGENT’]);
    //$this->userAgent=strtolower($this->userAgent);$this->device=array(‘iphone’,’ipad’,’android’,’silk’,’blackberry’, ‘touch’);
    $this->browser= array(‘firefox’,’chrome’,’opera’,’msie’,’safari’,’blackberry’,’trident’);
    $this->deviceLength=count($this->device);
    $this->browserLength=count($this->browser);
    }
    public function findDevice()
    {
    for($ii=0;$ii < $this->deviceLength;$ii ++)
    {
    if(strstr($this->userAgent,$this->device[$ii]))
    {
    //$device = $this->device[$ii];
    //if (is_null($device)) $device = ‘desktop/laptop’;
    //return $device;
    return $this->device[$ii];
    } else return ‘desktop/laptop’;
    }
    }//public function get_device() {
    //    return $this->vew_ibrowse_params();
    //} // e n dpublic function findBrowser()
    {
    for($ii=0;$ii < $this->browserLength;$ii ++)
    {
    if(strstr($this->userAgent,$this->browser[$ii]))
    {
    return $this->browser[$ii];
    }
    }
    }
    public function getUserAgent()
    {
    return($this->userAgent);
    }
    }?>
  3. J:\dev_web\inc\confglob.php
    <?php
    // J:\dev_web\inc\confglob.php - NOT WEB ACCESSIBLE
    //    - SAME LEVEL AS APACHE DOC ROOT
    ini_set("display_errors","2");
    ERROR_REPORTING(E_ALL);
    $md=realpath($_SERVER[‘DOCUMENT_ROOT’]);// 1. rel.adresses are ok for both  p a t h s  &   u r l s :
    $idxrel = str_replace($md,”, $idx); // str_replace(DS,’/’,
    $aplrel = str_replace($md,”, $apl); // str_replace(DS,’/’,// 2. u r l s  – s u b a p l,  a p l,  m d (main doc.root = Apache doc.root):
    $mdurl =’http://’.$_SERVER[“SERVER_NAME”].’:’.$_SERVER[“SERVER_PORT”];
    if (!defined(‘MDURL’)) define(‘MDURL’, $mdurl);
    $idxurl = $mdurl.str_replace(DS,’/’,$idxrel).’/’.$idxscript;
    $aplurl = $mdurl.’/’.substr(str_replace(DS,’/’,$aplrel),1);
    $imgurl = $mdurl.’/inc/img’;
  4. J:\dev_web\inc\utl\kod_edit_run.php
    <?php
    function kod_edit_run($script_dir_path, $script_name, $web_docroot_url)
    {
    $ds = DIRECTORY_SEPARATOR;
    echo <<< EOKOD
    <a href="$web_docroot_url/inc/utl/showsource.php
    ?file=$script_dir_path$ds$script_name
    &line=1&prev=10000&next=10000
    "  target="_blank">&nbsp;kod</a>
    <a href=”$web_docroot_url/inc/utl/edservertxt.php
    ?file=$script_dir_path$ds$script_name
    ” target=”_blank”>&nbsp;edit</a><a href=”$web_docroot_url/phpinfo_inc.php
    ” target=”_blank”>&nbsp;phpinfo</a>
    EOKOD;
    /* call it so:
    kod_edit_run(
    $idx       // script_dir_path
    , $idxscript // script_name
    , MDURL);    // web_docroot_url = (Apache) web server URL
    */
    }