use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, OnceLock, RwLock};
mod functions;
use claviron_runtime::AppState;
pub use ext_php_rs::builders::{EnumBuilder, FunctionBuilder, ModuleBuilder};
pub use ext_php_rs::class::RegisteredClass;
pub use ext_php_rs::enum_::{Discriminant, EnumCase, RegisteredEnum};
pub use ext_php_rs::flags::DataType;
pub use ext_php_rs::types::Zval;
pub use ext_php_rs::zend::{ExecuteData, FunctionEntry};
pub use ext_php_rs::{self, php_class, php_enum, php_impl};
pub use functions::FunctionEntries;
pub use crate::observer::{
BacktraceFrame, ErrorInfo, ErrorType, ExceptionInfo, FcallInfo,
PhpObserver, PhpObserverInterests,
};
pub trait PhpEmbedExtension: Send + Sync + 'static {
fn name(&self) -> &'static str;
fn configure(
&self,
_state: &AppState,
) -> Result<(), String> {
Ok(())
}
fn append_functions(
&self,
_entries: &mut FunctionEntries,
) -> Result<(), String> {
Ok(())
}
fn register_classes<'a>(
&self,
module: ModuleBuilder<'a>,
) -> Result<ModuleBuilder<'a>, String> {
Ok(module)
}
fn ensure_for_current_thread(&self) -> Result<(), String> {
Ok(())
}
fn observer(&self) -> Option<Arc<dyn PhpObserver>> {
None
}
}
static EXTENSIONS: OnceLock<RwLock<Vec<Arc<dyn PhpEmbedExtension>>>> =
OnceLock::new();
static EXTENSIONS_FROZEN: AtomicBool = AtomicBool::new(false);
fn registry() -> &'static RwLock<Vec<Arc<dyn PhpEmbedExtension>>> {
EXTENSIONS.get_or_init(|| RwLock::new(Vec::new()))
}
pub fn register_extension(
state: &AppState,
extension: Arc<dyn PhpEmbedExtension>,
) -> Result<(), String> {
if EXTENSIONS_FROZEN.load(Ordering::Acquire) {
return Err("PHP embed extension registry already frozen".to_string());
}
extension.configure(state)?;
let mut guard =
registry().write().map_err(|_| "PHP extension registry poisoned")?;
if EXTENSIONS_FROZEN.load(Ordering::Acquire) {
return Err("PHP embed extension registry already frozen".to_string());
}
if guard.iter().any(|registered| registered.name() == extension.name()) {
return Ok(());
}
guard.push(extension);
Ok(())
}
pub(crate) fn extensions_snapshot() -> Vec<Arc<dyn PhpEmbedExtension>> {
registry().read().map(|guard| guard.clone()).unwrap_or_default()
}
pub(crate) fn freeze_extensions()
-> Result<Vec<Arc<dyn PhpEmbedExtension>>, String> {
let guard =
registry().write().map_err(|_| "PHP extension registry poisoned")?;
EXTENSIONS_FROZEN.store(true, Ordering::Release);
Ok(guard.clone())
}
#[cfg(test)]
#[path = "../tests/extension.rs"]
mod tests;