11 Commits
1.0.2 ... 1.0.5

Author SHA1 Message Date
7b02960b46 added a skeleton for faster deployment 2025-06-08 18:19:12 -07:00
bedf615ad3 Minor documentation updates 2025-05-23 15:02:17 -07:00
5d2281b713 documentation update 2025-04-25 19:36:56 -07:00
7e877465a6 Ready to release 1.0.4 2025-04-22 19:48:30 +00:00
7360c279ae fixed logo size 2025-03-20 20:55:50 -07:00
641fdb17c5 updated logo 2025-03-20 18:49:52 -07:00
45e10dcacd updated logo 2025-03-20 18:49:52 -07:00
28513d367d functions and classes added. 2025-03-20 18:27:17 -07:00
0c41ca9b65 1.0.3
release with blank db working
2025-03-14 01:54:52 +00:00
d183c4c1e0 Database allowed empty, twig grabs global vars 2025-03-03 12:23:02 -08:00
f76bbfb27c docs for quick install 2025-02-07 16:17:55 -08:00
27 changed files with 378 additions and 130 deletions

View File

@@ -1,4 +1,4 @@
![Novaconium PHP](/_assets/header.svg) ![Novaconium PHP](/_assets/novaconium-logo.png)
# Novaconium PHP: A PHP Framework Built from the Past # Novaconium PHP: A PHP Framework Built from the Past
@@ -11,12 +11,25 @@ Master Repo: https://git.4lt.ca/4lt/novaconium
## Getting Started ## Getting Started
### Installation Novaconium is heavly influenced by docker, but you can use composer outside of docker.
You can [learn more about how novaconium works with composer](https://git.4lt.ca/4lt/novaconium/src/branch/master/docs/Install-Composer-On-Debian.md).
```bash ```bash
mkdir project_name; PROJECTNAME=novaproject
cd project_name; mkdir -p $PROJECTNAME/novaconium;
composer require 4lt/novaconium cd $PROJECTNAME;
cp -R vendor/4lt/novaconium/examples/App/ .
cp -R vendor/4lt/novaconium/examples/public/ . docker run --rm --interactive --tty --volume ./novaconium/:/app composer:latest require 4lt/novaconium
cp -R novaconium/vendor/4lt/novaconium/skeleton/. .
# Edit .env
# Edit novaconium/App/config.php
docker compose up -d
``` ```
## Documentation
* [Novaconiumm Official Repo](https://git.4lt.ca/4lt/novaconium)
* [CORXN Apache and PHP Container for Novaconium](https://git.4lt.ca/4lt/CORXN)

View File

@@ -1,39 +0,0 @@
<svg width="100%" height="200" xmlns="http://www.w3.org/2000/svg">
<rect width="100%" height="100%" fill="black"/>
<defs>
<radialGradient id="star-gradient" cx="50%" cy="50%" r="50%" fx="50%" fy="50%">
<stop offset="0%" style="stop-color: white; stop-opacity: 1" />
<stop offset="100%" style="stop-color: white; stop-opacity: 0" />
</radialGradient>
<g id="star">
<circle cx="0" cy="0" r="2" fill="white" />
</g>
</defs>
<!-- Lots of stars moving outward slower -->
<g>
<use href="#star" x="50%" y="50%" transform="translate(-300,-150) scale(1)" />
<use href="#star" x="50%" y="50%" transform="translate(300,-150) scale(1)" />
<use href="#star" x="50%" y="50%" transform="translate(-300,150) scale(1)" />
<use href="#star" x="50%" y="50%" transform="translate(300,150) scale(1)" />
<use href="#star" x="50%" y="50%" transform="translate(-150,-75) scale(0.5)" />
<use href="#star" x="50%" y="50%" transform="translate(150,-75) scale(0.5)" />
<use href="#star" x="50%" y="50%" transform="translate(-150,75) scale(0.5)" />
<use href="#star" x="50%" y="50%" transform="translate(150,75) scale(0.5)" />
<use href="#star" x="50%" y="50%" transform="translate(-75,-37) scale(0.7)" />
<use href="#star" x="50%" y="50%" transform="translate(75,-37) scale(0.7)" />
<use href="#star" x="50%" y="50%" transform="translate(-75,37) scale(0.7)" />
<use href="#star" x="50%" y="50%" transform="translate(75,37) scale(0.7)" />
<use href="#star" x="50%" y="50%" transform="translate(-350,-175) scale(0.4)" />
<use href="#star" x="50%" y="50%" transform="translate(350,-175) scale(0.4)" />
<use href="#star" x="50%" y="50%" transform="translate(-350,175) scale(0.4)" />
<use href="#star" x="50%" y="50%" transform="translate(350,175) scale(0.4)" />
<animateTransform attributeName="transform" type="scale" from="1" to="10" dur="2s" repeatCount="indefinite"/>
<animate attributeName="opacity" from="1" to="0" dur="2s" repeatCount="indefinite" />
</g>
<!-- Centered Title -->
<text x="50%" y="50%" fill="white" font-size="40" text-anchor="middle" font-family="Arial" dy=".3em">
Novaconium PHP
</text>
</svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

BIN
_assets/novaconium-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -1,7 +1,6 @@
{ {
"name": "4lt/novaconium", "name": "4lt/novaconium",
"description": "A high-performance PHP framework built from the past.", "description": "A high-performance PHP framework built from the past.",
"version": "1.0.2",
"license": "MIT", "license": "MIT",
"authors": [ "authors": [
{ {

View File

@@ -1,2 +0,0 @@
<?php
echo $twig->render('index.html.twig');

18
docs/Composer.md Normal file
View File

@@ -0,0 +1,18 @@
# PHP Composer Cheatsheet
Install novaconium with composer: ```composer require 4lt/novaconium```
Install novaconium with composer in docker: ```docker run --rm --interactive --tty --volume $PWD:/app composer:latest require 4lt/novaconium```
Update novaconium with composer in docker: ```docker run --rm --interactive --tty --volume $PWD:/app composer:latest update```
## Install Composer natively on Debian
Assuming you have nala installed:
```bash
sudo nala install curl php-cli php-mbstring git unzip
curl -sS https://getcomposer.org/installer -o composer-setup.php
sudo php composer-setup.php --install-dir=/usr/local/bin --filename=composer
rm composer-setup.php
```

9
docs/docker.md Normal file
View File

@@ -0,0 +1,9 @@
# Docker Cheatsheet (for Novaconium)
## Sample Docker Compose File
See the skeleton directory for an example docker setup.
## Start Docker
```docker compose up -d```

3
docs/twig-overrides.md Normal file
View File

@@ -0,0 +1,3 @@
# Twig Overrides
You can override twig templates by creating the same file in the templates directory.

2
skeleton/.env Normal file
View File

@@ -0,0 +1,2 @@
MYSQL_ROOT_PASSWORD: random
MYSQL_PASSWORD: random

View File

@@ -0,0 +1,56 @@
# Sample Docker Compose
services:
corxn:
image: 4lights/corxn:6.0.0
ports:
- "8000:80"
volumes:
- ./novaconium:/data
- ./data/logs:/var/log/apache2 # Optional Logs
restart: unless-stopped
networks:
- internal
- proxy
redis:
image: redis:latest
networks:
- internal
restart: unless-stopped
mariadb:
image: mariadb:latest
container_name: mariadb
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: novadb
MYSQL_USER: novaconium
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
volumes:
- ./data/db:/var/lib/mysql
networks:
- internal
restart: unless-stopped
phpmyadmin:
image: phpmyadmin/phpmyadmin:latest
restart: unless-stopped
ports:
- "8001:80"
networks:
- internal
environment:
- PMA_ARBITRARY=-1
- PMA_HOST=mariadb
- PMA_USER=root
- PMA_PASSWORD=${MYSQL_ROOT_PASSWORD}
- UPLOAD_LIMIT=200M
volumes:
- "/etc/timezone:/etc/timezone:ro"
- "/etc/localtime:/etc/localtime:ro"
networks:
proxy:
external: true
internal:
driver: bridge

View File

@@ -1,9 +1,9 @@
<?php <?php
$config = [ $config = [
'database' => [ 'database' => [
'host' => '', 'host' => 'mariadb',
'name' => '', 'name' => 'novadb',
'user' => '', 'user' => 'novaconium',
'pass' => '', 'pass' => '',
'port' => 3306 'port' => 3306
], ],

View File

@@ -0,0 +1,2 @@
<?php
view('index');

View File

@@ -2,71 +2,74 @@
class Database { class Database {
private $host;
private $user;
private $pass;
private $dbname;
private $conn; private $conn;
public function __construct($dbinfo) { public function __construct($dbinfo) {
$this->host = $dbinfo['host']; $this->conn = new mysqli($dbinfo['host'], $dbinfo['user'], $dbinfo['pass'], $dbinfo['name']);
$this->user = $dbinfo['user'];
$this->pass = $dbinfo['pass'];
$this->dbname = $dbinfo['name'];
$this->connect();
}
private function connect() {
$this->conn = new mysqli($this->host, $this->user, $this->pass, $this->dbname);
if ($this->conn->connect_error) { if ($this->conn->connect_error) {
die("Connection failed: " . $this->conn->connect_error); die("Connection failed: " . $this->conn->connect_error);
} }
} }
public function query($query) { public function query($query, $params = []) {
// Prepare the SQL query
if ($stmt = $this->conn->prepare($query)) {
// Bind parameters to the prepared statement (if any)
if (!empty($params)) {
$types = str_repeat('s', count($params)); // Assuming all params are strings
$stmt->bind_param($types, ...$params);
}
// Execute the statement
if (!$stmt->execute()) {
throw new Exception("Query execution failed: " . $stmt->error);
}
// Return the statement result
return $stmt;
} else {
throw new Exception("Query preparation failed: " . $this->conn->error);
}
}
public function getRow($query, $params = []) {
try {
// Perform the query using prepared statement
$stmt = $this->query($query, $params);
// Get the result of the query
$result = $stmt->get_result();
// Fetch the first row from the result
return $result->fetch_assoc();
} catch (Exception $e) {
// Handle the exception (log it, display a message, etc.)
echo "An error occurred: " . $e->getMessage();
return null;
}
}
public function getRows($query, $params = []) {
$stmt = $this->conn->prepare($query); $stmt = $this->conn->prepare($query);
if (!$stmt) {
die("Query preparation failed: " . $this->conn->error);
}
// Bind parameters if provided
if (!empty($params)) {
$types = str_repeat('s', count($params)); // Assuming all are strings, adjust as needed
$stmt->bind_param($types, ...$params);
}
$stmt->execute(); $stmt->execute();
$result = $stmt->get_result(); // Requires MySQL Native Driver (mysqlnd)
return $stmt->get_result();
}
public function getRow($query) {
$result = $this->query($query);
return $result->fetch_assoc();
}
public function debugGetRow($query) {
echo "<h1>Debug GetRow Query</h1>";
echo "<div class='debug-query'>Query: $query</div>";
$result = $this->query($query);
$row = $result->fetch_assoc();
echo "<pre>"; if ($result) {
print_r($row); return $result->fetch_all(MYSQLI_ASSOC);
echo "</pre>"; } else {
return [];
die(); }
}
public function getRows($query) {
$result = $this->query($query);
return $result->fetch_all(MYSQLI_ASSOC);
}
public function debugGetRows($query) {
echo "<h1>Debug GetRows Query</h1>";
echo "<div class='debug-query'>Query: $query</div>";
$result = $this->query($query);
$rows = $result->fetch_all(MYSQLI_ASSOC);
echo "<pre>";
print_r($rows);
echo "</pre>";
die();
} }
public function close() { public function close() {

62
src/MessageHandler.php Normal file
View File

@@ -0,0 +1,62 @@
<?php
class MessageHandler {
private $messages = [
'error' => [],
'warning' => [],
'notice' => [],
'success' => []
];
// Add a message of a specific type
public function addMessage($type, $message) {
if (!isset($this->messages[$type])) {
throw new Exception("Invalid message type: $type");
}
$this->messages[$type][] = $message;
}
// Get all messages of a specific type
public function getMessages($type) {
return $this->messages[$type] ?? [];
}
// Get all messages of all types
public function getAllMessages() {
return $this->messages;
}
// Get the count of messages for a specific type
public function count($type) {
return isset($this->messages[$type]) ? count($this->messages[$type]) : 0;
}
// Get the total count of all messages
public function totalCount() {
return array_sum(array_map('count', $this->messages));
}
// Check if there are any messages of a specific type
public function hasMessages($type) {
return !empty($this->messages[$type]);
}
// Check if there are any messages at all
public function hasAnyMessages() {
return $this->totalCount() > 0;
}
// Clear messages of a specific type
public function clear($type) {
if (isset($this->messages[$type])) {
$this->messages[$type] = [];
}
}
// Clear all messages
public function clearAll() {
foreach ($this->messages as $type => $list) {
$this->messages[$type] = [];
}
}
}

25
src/Post.php Normal file
View File

@@ -0,0 +1,25 @@
<?php
class Post {
private $data = [];
public function __construct($post) {
$this->sanitize($post);
}
private function sanitize($post) {
foreach ($post as $key => $value) {
$this->data[$key] = is_array($value)
? filter_var_array($value, FILTER_SANITIZE_FULL_SPECIAL_CHARS)
: filter_var($value, FILTER_SANITIZE_FULL_SPECIAL_CHARS);
}
}
public function get($key, $default = null) {
return $this->data[$key] ?? $default;
}
public function all() {
return $this->data;
}
}

40
src/Redirect.php Normal file
View File

@@ -0,0 +1,40 @@
<?php
/**
* Use
* $redirect->to('/login');
* to trigger a redirect
*/
class Redirect {
private ?string $url = null;
private int $statusCode = 303;
public function url(string $relativeUrl, int $statusCode = 303): void {
$this->statusCode = $statusCode;
// Detect HTTPS
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? "https" : "http";
// Get Hostname
$host = $_SERVER['HTTP_HOST'];
// Get Base Directory
$basePath = rtrim(dirname($_SERVER['SCRIPT_NAME']), '/\\');
// Construct Absolute URL
$this->url = "$protocol://$host$basePath/" . ltrim($relativeUrl, '/');
}
public function isset(): bool {
return !is_null($this->url);
}
public function execute(): void {
if ($this->url) {
header("Location: " . $this->url, true, $this->statusCode);
exit();
}
}
}

View File

@@ -119,19 +119,16 @@ class Router {
} }
public function debug() { public function debug() {
echo '<h1>Debugging Router</h1>'; echo '<div id="router-debug-container" class="debug">';
echo '<h2>Url Path</h2>'; echo '<table border="1" cellpadding="10" cellspacing="0">';
echo $this->path . '<br>'; echo '<tr><th>Url Path</th><td>' . htmlspecialchars($this->path) . '</td></tr>';
echo '<h2>ControllerPath</h2>'; echo '<tr><th>Controller Path</th><td>' . htmlspecialchars($this->controllerPath) . '</td></tr>';
echo $this->controllerPath; echo '<tr><th>Parameters</th><td><pre>' . print_r($this->parameters, true) . '</pre></td></tr>';
echo '<h2>Parameters</h2>'; echo '<tr><th>Routes</th><td><pre>' . print_r($this->routes, true) . '</pre></td></tr>';
echo '<pre>'; echo '</table></div>';
print_r($this->parameters);
echo '</pre>';
echo '<h2>Routes Variable</h2><pre>';
print_r($this->routes);
echo '</pre>';
die(); die();
} }
} }

View File

@@ -4,13 +4,15 @@ class Session {
private $session; private $session;
public function __construct() { public function __construct() {
session_start();
if (!isset($_SESSION)) { if (!isset($_SESSION)) {
session_start();
$this->session = $_SESSION; $this->session = $_SESSION;
$this->setToken();
$this->session['messages'] = [];
} else { } else {
$this->session = $_SESSION; $this->session = $_SESSION;
} }
$this->setToken();
} }
public function setToken() { public function setToken() {
@@ -34,7 +36,7 @@ class Session {
} }
public function debug() { public function debug() {
print_r($this->session); return $this->session;
} }
public function delete($key) { public function delete($key) {
@@ -48,4 +50,8 @@ class Session {
session_write_close(); session_write_close();
} }
public function kill() {
session_destroy();
}
} }

29
src/functions.php Normal file
View File

@@ -0,0 +1,29 @@
<?php
/**
* Dump and Die
*/
function dd(...$vars) {
echo "<pre style='background:#222;color:#0f0;padding:10px;border-radius:5px;'>";
foreach ($vars as $var) {
var_dump($var);
echo "\n";
}
echo "</pre>";
die();
}
function makeitso() {
global $session, $db, $redirect, $config, $messages;
if (!empty($config['database']['host'])) {
$db->close();
}
$session->set('messages', $messages->getAllMessages());
$session->write();
$redirect->execute();
exit();
}

View File

@@ -10,16 +10,35 @@ if (file_exists(BASEPATH . '/App/config.php')) {
require_once(FRAMEWORKPATH . '/defaults/App/config.php'); require_once(FRAMEWORKPATH . '/defaults/App/config.php');
} }
// Creates twig and the view() function // Global Functions
require_once(FRAMEWORKPATH . '/src/functions.php');
// Creates the view() function using twig
require_once(FRAMEWORKPATH . '/src/twig.php'); require_once(FRAMEWORKPATH . '/src/twig.php');
// Messages
require_once(FRAMEWORKPATH . '/src/MessageHandler.php');
$messages = new MessageHandler;
// Start a Session // Start a Session
require_once(FRAMEWORKPATH . '/src/Session.php'); require_once(FRAMEWORKPATH . '/src/Session.php');
$session = new Session(); $session = new Session();
// Load Database Class // Load Database Class
require_once(FRAMEWORKPATH . '/src/Database.php'); if (!empty($config['database']['host'])) {
$db = new Database($config['database']); require_once(FRAMEWORKPATH . '/src/Database.php');
$db = new Database($config['database']);
}
// Sanatize POST Data
if (!empty($_POST)) {
require_once(FRAMEWORKPATH . '/src/Post.php');
$post = new POST($_POST);
}
// Start a Redirect
require_once(FRAMEWORKPATH . '/src/Redirect.php');
$redirect = new Redirect();
// Load a controller // Load a controller
require_once(FRAMEWORKPATH . '/src/Router.php'); require_once(FRAMEWORKPATH . '/src/Router.php');
@@ -27,5 +46,4 @@ $router = new Router();
//$router->debug(); //$router->debug();
require_once($router->controllerPath); require_once($router->controllerPath);
//write the session makeitso();
$session->write();

View File

@@ -1,14 +1,21 @@
<?php <?php
//Twig //Twig
function view($name = '', $data = [] ) { function view($name = '', $data = []) {
global $config; // Use the globally included $config
$loader = new Twig\Loader\FilesystemLoader(BASEPATH . '/App/views/'); $loader = new Twig\Loader\FilesystemLoader(BASEPATH . '/App/views/');
$loader->addPath(BASEPATH . '/vendor/4lt/novaconium/twig', 'novaconium'); $loader->addPath(BASEPATH . '/vendor/4lt/novaconium/twig', 'novaconium');
$loader->addPath(BASEPATH . '/App/templates', 'override'); $loader->addPath(BASEPATH . '/App/templates', 'override');
$twig = new Twig\Environment($loader);
//check if file exists $twig = new Twig\Environment($loader);
// Add config to Twig globally
$twig->addGlobal('config', $config);
// Check if the template exists
if (file_exists(BASEPATH . '/App/views/' . $name . '.html.twig')) { if (file_exists(BASEPATH . '/App/views/' . $name . '.html.twig')) {
echo $twig->render("$name" . '.html.twig', $data); echo $twig->render("$name.html.twig", $data);
return true; return true;
} else { } else {
echo "Error: Twig Template ($name) Not Found."; echo "Error: Twig Template ($name) Not Found.";