A SvelteKit adapter-static plugin that enables seamless PHP backend integration using SvelteKit-style file conventions (e.g., +page.server.php).
Combine the best of Svelte and PHP in your project.
+.php files in your project just like SvelteKit’s +server.js|ts.npm run dev and use +.php files inside your /src/routes.npm run build and deploy to your production environment. Access your app at http://domain/<build>.Follow the installation steps 1 - 5.
Create your SvelteKit project
npx sv create myZPproject
Be sure to use adapter-static and place your project in your local DOCUMENT_ROOT directory. e.g. /htdocs. 
Install ZeeltePHP
npm add github:derharry/svelte-zeeltephp
Or use a .tgz file.
Add Vite Plugin
Update your vite.config.js:
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
import { zeeltephp } from 'zeeltephp/vite-plugin';   // add
   
export default defineConfig(({ mode }) => {     // add mode
   return {
      plugins: [
         zeeltephp(mode),   // add, before sveltekit()
         sveltekit(),
      ]
   }
});
Note: the vite-plugin creates, if not exist, following paths in your project:
/src/lib_php            # Shared PHP library for your +.php files
/src/routes/+layout.js  # Required by adapter-static
/static/api/index.php   # PHP api entry point
/php_log                # PHP error and log output
Start Development (Optional)
npm run dev
Open http://localhost:5173/myZPproject to check if your app is working.
If you encounter issues, check the CLI output for 🐘 ZeeltePHP and ensure the required paths have been created.
Demo & Debug (Optional)
Copy /zpdemo/ into /src/routes/ and verify it runs in development mode.
Green icons and receiving responses? Have fun SveltePHP'ing.
Configure for Build
Update svelte.config.js to use .env variables:
import adapter from '@sveltejs/adapter-static';
const config = {
   kit: {
      adapter: adapter({
         pages:  process.env.BUILD_DIR,  // add
         assets: process.env.BUILD_DIR,  // add
      }),
      paths: { 
         base: process.env.BASE          // add
      }
   }
};
export default config;
(Optional)
npm run build
Open http://localhost/myZPproject/build/ and see if your build is working. 
Configure .env for Build or Final Production Deployment
Configure your .env.production for deployment (e.g. export/FTP).
See Description/.env Configuration for more details.
BUILD_DIR=../build-prod                 # build-output folder; e.g. http://localhost/build-prod
BASE=/build-prod                        # starting from `DOCUMENT_ROOT`; e.g. http://www.domain.com/build-prod
PUBLIC_ZEELTEPHP_BASE=/build-prod/api/  # Same as BASE but ends with `api/`
Note: If no .env.production is specified, the variables are auto-generated and will run by default as: 
  http://localhost/myZPproject/build or https://www.domain.com/myZPproject/build.
npm remove zeeltephp
Production  https://www.example.com/<my-build> 
Development http://localhost/<my-project>/<my-build> 
+page.server.php<?php
      function load() {
            return 'Hello PHP';
            // or
            return [
                  'message' => 'Hello from PHP',
            ];
      }
      function actions($action, $value) {
            global $db;
            // e.g. ?/myAction from SvelteKit.
            switch ($action) {
                  case 'myAction':
                              return "Hello myAction from PHP. received value: $value";
                        break;
                  case 'myDBrequestAction':
                              $result = $db->query("SELECT 'Hello from DB' AS message;");  
                              return $result;
                              // You can use $db for your own DB logic.
                              // When .env.ZEELTEPHP_DATABASEURL is used, ZeeltePHP uses the internal db.PROVIDER.php.
                        break;
            }
    }
      function action_myAction($value) {
            // This 'outside' action-method takes precedence over actions() 
            $result = myLibFunction($value);  // from Shared PHP lib which is pre-loaded. No include() required.
            return $result;
      }
?>
+page.jsimport { zp_fetch_api } from "zeeltephp";
export async function load({ fetch, url }) {
      const result_php = await zp_fetch_api(fetch, url);
      or 
      const promise_php = zp_fetch_api(fetch, url);
      return [
            message: 'Hello from +page.js',
            result_php,
            promise_php
      ];
}
+page.svelte<script>
      import { zp_fetch_api } from "zeeltephp";
      export let data;  
      let promise = data?.promise_php || undefined;
      async function handle_click(event) {
            promise = zp_fetch_api(fetch, event)
                  .then(data => {})
                  .catch(error => {})
      }
      async function handle_submit(event) {
            const data = await zp_fetch_ap(fetch, event);
      }
</script>
<button
      type="button"
      formaction="?/myAction"
      value="1"
      on:click={handle_click}
/>
<form on:submit={handle_submit}>
      <button
            type="submit"
            formaction="?/myAction"
            value="42"
      />
      {#await promise}
            ..
      {:then data}
            ..
      {:catch error}
            .. 
      {/await}
</form>
/zpdemo into your /routes and see if it works.zp_fetch_api(), ZP_EventDetails(event), and ZP_ApiRouter().zeeltephp(mode).env file and generates missing variables./src/lib/lib_php/ → /BUILD/api/zeeltephp/lib_php
+.php files are called./src/routes/** → /BUILD/api/zeeltephp/zproutes
+.php files. /static/api/index.php → /BUILD/api/zeeltephp/index.php
/php_lib, /BUILD/api/zeeltephp/php_lib/
# ignore PHP error and logs
/php_lib
The /zpdemo/ folder serves as a complete demo and debugger. 
Copy it into your routes and access it at http://localhost/myZPproject/zpdemo.
If zpdemo does not work, there may be a misconfiguration from the installation steps. 
See troubleshooting for more details.
zp_fetch_api(fetch, router, [, data, method, headers]) (Svelte)  Handles most use cases for fetching data from the PHP api.
  
 Uses Svelte's fetch which needs to be passed as parameter and ZP_ApiRouter for the routing details.
  
 This method has overloads. See zp.fetch.api.js / zp_fetch_api() for more details.
zp_fetch_api(fetch, Event [, ..])            // AnyEventType; will be parsed by ZP_EventDetails 
zp_fetch_api(fetch, URL|URLParams, [, ..])   // Will be parsed by ZP_EventDetails
zp_fetch_api(fetch, ZP_ApiRouter [, ..])     // If you created (or modified) ZP_ApiRouter earlier.
zp_fetch_api(fetch, ZP_EventDetails [, ..])  // If you created (or modified) ZP_EventDetails earlier.
ZP_ApiRouter (Svelte and PHP)  
Prepares the request (at Svelte) and destructs the route (at PHP) for +.php. 
ZP_EventDetails:  
 Collects information from Events like actions and data to transfer. 
ZPDev.svelte:  
Debugging component for your +page.server.php files. It's initial settings work with ZPDemo
VarDump.svelte:  
Shows (dumps) the content of a variable visually in UI. Like PHPs var_dump().
/BUILD_DIR/api/zeeltephp/.env.
```sh
BUILD_DIR=myBuild
# development:  empty; not used
# build:        build is saved to /myZPproject/myBuild;
#    or:        ../myStageTest
# production:   mySITE;  Build-name to export/FTP and on to run on final-destination; 
#                        For export the folder can be any naming e.g. myExportBuild, as long it is renamed
#                        to as specified at BASE at its final destination to run your App e.g. mySITEBASE=/myZPproject/myBuild # development: empty # build: /myZPproject/myBuild; http://localhost/myZPproject/myBuild # or: /myStageTest; http://localhost/myStageTest # production: /mySITE ; http://www.domain.com/mySITE # in root: / ; http://www.domain.com/
PUBLIC_ZEELTEPHP_BASE=/myZPproject/myBuild/api # development: http://localhost/myZPproject/static/api/ # build: /myZPproject/myBuild/api/ # or: /myStageTest/api/ # production: /mySITE/api/ # in root: /api/
---
### PHP
#### PHP error log
- Errors are logged to `/php_log`, `/BUILD/api/zeeltephp/php_log/` as long its allowed to be set via `ini_set('error_log')`.
- At error output (log, responses) 
  - the path `/api/zeeltephp` is shortened to `/api` for readability.
  - full system paths are changed to relative paths.
- For saving custom log-files you can use `PATH_ZPLOG`.
  - e.g. `file_put_contents(PATH_ZPLOG."mylog.txt")`.
  - **zp_log( $content )** writes the content to `log.log`.
#### Globals
These global variables are accessible from anywhere in your PHP code.
- **$zpAR**: is class ZP_ApiRouter; 
- **$env**:  The exported `.env` configuration. 
- **$db**:   Database provider (if configured). Or use it for your custom shared DB-connection.
#### Database Provider 
***experimental!  in development!***
<br>There are currently 2 classes for MySQL and WordPress available at `/api/zeeltephp/lib/db`. 
<br>They can be used for any custom query and contain some helper methods for like basic CRUD operations.
<br>Any attribute or method that is available at `mysqli` or `wp-load` can be used (magic getter and method forwarding).
<br>
<br>When `.env.ZEELTEPHP_DATABASE_URL` is set, ZeeltePHP uses the corresponding internal database provider class.
```sh
ZEELTEPHP_DATABASE_URL=mysql2://username:password@hostname:port/database
ZEELTEPHP_DATABASE_URL=wordpress://path/to/your/wp-load.php
   function example_db_usage() {
      global $db;
      // optional connect
      //   the connect() is automatically opened as soon any $db->METHOD() is called;
      $db->connect();
      // is there a connection ?
      if ($db->connect()) {
            // returns true or false on succes 
            // returns true when already connected to avoid multiple connections.
      }
      if ($db->isConnected()) {
            // returns true or false if is connected.
      }
      // just a query
      $res = $db->query('SELECT * FROM ...'); 
      // prepare data for CRUD operations
      //  * $data must be an array key-value-list where key = column-name.
      //  * The data gets prepared, sanitized for matching key/columns. 
      //  * non matching key/columns will be ignored.
      $data = $_POST;
      // insert data
      //   returns the inserted ID on success;  false on failure
      $insertedID = $db->insert('tableName', $data); 
      // update data
      //  returns true of false; 
      //       true  on 1+ affected-rows
      //       false on 0  affected-rows
      //  * id, uuid are ignored if in $data
      //  @param id is an array key-value-list of matches combined with AND
      $success = $db->update('tableName', ['id' => $id], $data);
                        
      // delete data
      //  returns true of false; 
      //       true  on 1+ affected-rows
      //       false on 0  affected-rows
      //  @param id is an array key-value-list of matches combined with AND
      $success = $db->delete('tableName', ['id' => $id]);
      // the number of affected rows of last query
      $affectedRows = $db->affected_rows;
      // the error message of last query
      $error = $db->last_error();
      // optional disconnect
      //   the connection is automatically closed at __destruct();
      $db->close();
   }
At /api/zeeltephp/lib/ you’ll find a collection of useful helper methods.
To use them you have to include them in your PHP code.
Currently:
merge_key_value_list(array $src, array $insert): array 
include_once('lib/inc/tools.php');
returns a merged key-value-list by copying the values from $insert matching the keys from $src.
e.g.:  $list = merge_key_value_list( $toUpdate, $_POST );
zp_scandir($path, $regExp = null): array 
include_once('lib/io/io.dir.php');
returns a list of files and folders within the path. Use a RegExp like '.php$' for name matching.
zp_scandirRecursiveDown($path, $regExp = null, $maxDepth = 1): array 
include_once('lib/io/io.dir.php');
returns a list of files and folders, recursive downwards, with relative paths starting from given path. 
By default only 1 level down.
Use a RegExp like '.php$' for name matching.
zp_scandirRecursiveUp($path, $regExp = null, $maxDepth = 1): arrayinclude_once('lib/io/io.dir.php');
returns a list of files and folders, recursive upwards, with relative paths starting from given path. 
By default only 1 level up, which is similar to zp_scandir('..'); but $maxDepth = 2 is '..' and '../..';
Use a RegExp like '.php$' for name matching.
If you encounter issues with your route, load(), or actions(), you can debug your setup by including <ZPDev /> in your +page.svelte.
This enables you to use the +page.server.php file to execute load(), edit action names/values, and send test data as either FormData or JSON.
Make sure the global variables $zpAR, $env, and $db are accessible and exposed.
Attention! 
Don't forget to remove <ZPDev /> and exposed globals before deploying to production to avoid exposing debug tools and sensitive data.
+page.svelte or in a Component.
<script>
     import { ZPDev } from "zeeltephp";
</script>
<ZPDev />
+page.server.php
<?php
      function load() {
            // load(), actions()
            global $zpAR, $env, $db;
            return [
                  'zpAR'  => $zpAR, // expose ApiRouter of PHP
                  'zpEnv' => $env,  // expose your exported .env
                  'zpDB'  => $db    // export your DB or ZP-Database-provider connection
            ];
      }
?>
For further questions, support, contribution or suggestions feel free to contact the maintainer.