SqlP is a powerful ORM-like bridge between PHP and SQL in Vvveb CMS.
It allows developers to write native SQL queries in .sql files while automatically generating PHP classes to interface with them.
This approach maintains the performance of raw SQL without the "spaghetti code" of embedding long query strings inside PHP files.
File Structure
SQL definitions are stored based on the database engine to ensure cross-compatibility:
app/sql/{db-engine}/{filename}.sql
- db-engine:
mysqli,pgsql, orsqlite. - Automatic Generation: For every
.sqlfile, Vvveb generates a corresponding PHP class on the fly (e.g.,product.sqlbecomesProductSQLclass).
Procedure Definition
Procedures are defined using a syntax similar to standard SQL CREATE PROCEDURE, but they include special IN and OUT parameters to handle PHP data mapping.
PROCEDURE getAll(
IN product_id INT,
IN slug CHAR,
IN language_id INT,
OUT fetch_row,
OUT fetch_all,
OUT fetch_one,
OUT insert_id,
OUT affected_rows
)
BEGIN
END
Parameter Types
| Type | Description |
|---|---|
| IN | Input variables passed from PHP (e.g., INT, CHAR, ARRAY). |
| OUT | Defines the return format of the query results. |
Output Types (OUT)
fetch_all: Returns an array of all matching rows.fetch_row: Returns a single associative array (forLIMIT 1).fetch_one: Returns a single scalar value (e.g., a count or a specific column).insert_id: Returns the ID generated by the lastINSERTstatement.affected_rows: Returns the number of rows modified byUPDATEorDELETE.
SQL Example (product.sql)
-- Products
-- get one product
CREATE PROCEDURE get(
IN product_id INT,
IN slug CHAR,
OUT fetch_row
)
BEGIN
-- product
SELECT *
FROM product as _ -- (underscore) _ means that data will be kept in main array
WHERE 1 = 1
@IF isset(:product_id)
THEN
AND product_id = :product_id
END @IF
@IF isset(:slug)
THEN
AND slug = :slug
END @IF
LIMIT 1;
END
-- Edit product
CREATE PROCEDURE edit(
IN product ARRAY,
IN product_id INT,
OUT affected_rows
)
BEGIN
-- SELECT * FROM product_option WHERE product_id = :product_id;
-- allow only table fields and set defaults for missing values
@FILTER(:product, product);
UPDATE product
SET @LIST(:product)
WHERE product_id = :product_id
END
-- Add new product
CREATE PROCEDURE add(
IN product ARRAY,
IN site_id INT,
OUT insert_id,
OUT insert_id
)
BEGIN
-- allow only table fields and set defaults for missing values
:product = @FILTER(:product, product);
INSERT INTO product
( @KEYS(:product) )
VALUES ( :product );
INSERT INTO product_to_site
( product_id, site_id )
VALUES ( @result.product, :site_id );
END
-- get all products
CREATE PROCEDURE getAll(
-- variables
IN language_id INT,
IN user_group_id INT,
IN site_id INT,
IN search CHAR,
-- pagination
IN start INT,
IN limit INT,
-- return array of products for products query
OUT fetch_all,
-- return products count for count query
OUT fetch_one,
)
BEGIN
SELECT * FROM product AS product
LEFT JOIN product_to_site p2s ON (product.product_id = p2s.product_id)
WHERE p2s.site_id = :site_id
@IF isset(:search)
THEN
AND name LIKE :search
END @IF
LIMIT :start, :limit;
SELECT count(*) FROM (
@SQL_COUNT(product.product_id, product) -- this takes previous query removes limit and replaces select columns with parameter product_id
) as count;
END
-- delete product
CREATE PROCEDURE delete(
IN product_id ARRAY,
OUT affected_rows
)
BEGIN
DELETE FROM product WHERE product_id IN (:product_id)
END
Will generate a file productsql.mysqli.php with the following structure:
<?php
/*
Generated from /home/www/vvveb/vvveb/admin/sql/mysqli//product.sql
*/
namespace Vvveb\Sql;
use \Vvveb\System\Db;
class ProductSQL {
function get($params = array()) {
}
function edit($params = array()) {
}
function add($params = array()) {
}
function getAll($params = array()) {
}
function delete($params = array()) {
}
}
PHP Usage
Once the .sql file is saved, use the generated class in your PHP code:
use Vvveb\Sql\ProductSQL;
$db = new ProductSQL();
// Fetch data (returns array/object based on OUT param)
$product = $db->get(['product_id' => 3]);
// Get first 3 products
$data = $product->getAll(['limit' => 3);
// Insert data
$newId = $db->add([
'product' => ['name' => 'New Camera', 'price' => 599],
'site_id' => 1
]);
//edit product with product_id = 1
$product->edit(['product' => ['name' => 'Product 2', 'price' => 100], 'product_id' => 1]);
//delete product with product_id = 3
$data = $product->delete(['product_id' => 3);
Macros
SqlP uses custom macros to handle dynamic logic directly within the SQL file.
1. Logic Macros (@IF, @ELSE)
If macro
@IF isset(:product_id)
THEN
AND product_id = :product_id
END @IF
With @IF you can conditionally include sql code, the condition is valid native PHP code, you can use any php code with multiple conditions or different functions like
@IF isset(:product_id) AND !empty(:product_id)
If else macro
@IF isset(:product_id)
THEN
AND product_id = :product_id
@ELSE
AND product_id = 0
END @IF
With @IF @ELSE macro
@IF isset(:product_id) AND !empty(:product_id)
2. Helper Macros
@SQL_COUNT(col, table): Automatically generates a count query based on the precedingSELECTstatement, removing limits and optimizing columns.@LIST(:array): Expands an array intocol1=val1, col2=val2(ideal forUPDATE).@KEYS(:array): Returns a comma-separated list of array keys (ideal forINSERT).@SQL_LIMIT(:start, :limit): Handles cross-database syntax differences for pagination.
SQL_COUNT
Use this macro to run sql count for the previous query to avoid duplicating the query code. The macro will automatically replace all the columns (product_id, name) from the query with the first parameter products.product_id, the second parameter is the table name.
SELECT product_id, name FROM products
INNER JOIN product_content pc ON pc.product_id = product.product_id
WHERE vendor_id = 1;
SELECT count(*) FROM (
@SQL_COUNT(products.product_id, product) -- this takes previous query removes limit and replaces select columns with parameter product_id
) as count;
LIST
Will expand the array into a column=value list in the format used by UPDATE queries.
column1=value, column2=value2, column3=value3
UPDATE product
SET @LIST(:product_update)
3. Loop Macro (@EACH)
EACH
Repeats a query for every item in a PHP array, :each is the item in the array
@EACH(:product_digital_asset)
INSERT INTO product_to_digital_asset
( digital_asset_id, product_id)
VALUES ( :each, :product_id );
-- example with KEYS macro
@EACH(:product_attribute)
INSERT INTO product_attribute
( @KEYS(:each), product_id)
VALUES ( :each, :product_id );
KEYS
Keys macro will use the key instead of value for array values similar to php array_keys
In the following example
@EACH(:product_attribute)
INSERT INTO product_attribute
( @KEYS(:each), product_id)
VALUES ( :each, :product_id );
$product_attribute = [1 => ['name' => 'Product 1', 'price' => 30], 2 => 'Product 2', 'price' => 50];
:each will be
'Product 1', '30'
and @KEYS(:each) will be
'name', 'price'
SQL limit
Use this macro to avoid LIMIT syntax differences between pgsql and mysql
@IF isset(:limit)
THEN
@SQL_LIMIT(:start, :limit)
END @IF;
array_key and array_value column alias
With array_key and array_value column aliases you can change the format of the returned results to be more compact.
Use these aliases to return custom associative arrays instead of standard nested lists.
For example
-- stock status
SELECT
stock_status_id
name
FROM stock_status as stock_status_id;
will return
['stock_status_id' =>
[
0 => ['stock_status_id' => 5, 'name' => 'status 1' ],
1 => ['stock_status_id' => 8, 'name' => 'status 2' ],
2 => ['stock_status_id' => 9, 'name' => 'status 3' ]
]
];
with the aliases
-- stock status
SELECT
stock_status_id as array_key, -- stock_status_id as key
name as array_value -- only set name as value and return
FROM stock_status as stock_status_id;
the return data will be
['stock_status_id' =>
[
5 => 'status 1',
8 => 'status 2',
9 => 'status 3'
]
];
with only array_key alias
-- stock status
SELECT
stock_status_id as array_key, -- stock_status_id as key
name
FROM stock_status as stock_status_id;
the return data will be
['stock_status_id' =>
[
5 => ['stock_status_id' => 5, 'name' => 'status 1' ],
8 => ['stock_status_id' => 8, 'name' => 'status 2' ],
9 => ['stock_status_id' => 9, 'name' => 'status 3' ]
]
];
Result Formatting & Aliases
_ table alias
By default, SqlP wraps results in an array keyed by the {table name}. Use the _ alias to flatten the result.
- Standard:
['product' => ['name' => 'Phone']] - With
AS _:['name' => 'Phone']
For example with
-- select example
-- product
SELECT product.*
FROM product
WHERE product.vendor_id = 1 LIMIT 1;
The values will be returned in the array using the table name for array key
['product' => [
['name' => 'product 1],
['name' => 'product 2],
]
]
With the _ alias
-- select example
-- product
SELECT product.*
FROM product
WHERE product.vendor_id = 1 LIMIT 1;
The values will be returned without the table name as key and the values will be appended to the array directly
[
['name' => 'product 1],
['name' => 'product 2],
]
@result.table_name
When you need to use a value from a previous query like a last_insert id or a column id you can use @result.table_name
Access data from a previous query within the same procedure.
@result.table_name: Returns the last insert ID or row from that table.@result.column_name: If using the_alias, access the column value directly.
-- select example
-- product
SELECT product.*
FROM product
WHERE product.vendor_id = 1 LIMIT 1;
-- subscription
SELECT product_subscription.*
FROM product_subscription
WHERE product_subscription.product_id = @result.product;
-- insert example
-- product
INSERT INTO product
( @KEYS(:product_data) )
VALUES ( :product_data );
-- subscription
SELECT product_subscription.*
FROM product_subscription
WHERE product_subscription.product_id = @result.product;
When you use _ for table name alias because values are in the top level array you can use @result.column_name directly
-- select example
-- product
SELECT product.*
FROM product AS _
WHERE product.vendor_id = 1 LIMIT 1;
-- subscription
SELECT product_subscription.*
FROM product_subscription
WHERE product_subscription.product_id = @result.product_id;