Skip to content

Commit 4e73dd5

Browse files
jakubkulhanclaude
andcommitted
Replace PHP interface loading with native Rust implementation and reorganize module structure
🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 1896807 commit 4e73dd5

10 files changed

Lines changed: 615 additions & 525 deletions

File tree

data-access-kit-replication/src/checkpointer.rs

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,129 @@
11
use ext_php_rs::prelude::*;
2+
use ext_php_rs::ffi;
23
use ext_php_rs::types::Zval;
4+
use ext_php_rs::zend::{ClassEntry, ZendType};
5+
use ext_php_rs::flags::{ClassFlags, DataType};
6+
use std::ffi::CString;
7+
use std::mem;
8+
use std::ptr;
9+
10+
// Global pointer to StreamCheckpointerInterface
11+
static mut CHECKPOINTER_INTERFACE: *mut ClassEntry = ptr::null_mut();
12+
13+
// Unsafe function to register StreamCheckpointerInterface
14+
pub unsafe fn register_checkpointer_interface() {
15+
// Create and register StreamCheckpointerInterface
16+
let mut interface_ce: ffi::zend_class_entry = mem::zeroed();
17+
18+
// Set the interface name
19+
let name = CString::new("DataAccessKit\\Replication\\StreamCheckpointerInterface").unwrap();
20+
interface_ce.name = ffi::ext_php_rs_zend_string_init(
21+
name.as_ptr(),
22+
name.as_bytes().len(),
23+
true
24+
);
25+
26+
// Set interface flags
27+
interface_ce.ce_flags = ClassFlags::Interface.bits();
28+
29+
// Create function entries for the interface methods
30+
let mut functions: Vec<ffi::zend_function_entry> = Vec::new();
31+
32+
// Create arginfo for loadLastCheckpoint method (no parameters, returns ?string)
33+
let mut load_arg_infos: Vec<ffi::zend_internal_arg_info> = Vec::new();
34+
35+
// First element: metadata (return type ?string, 0 required args)
36+
load_arg_infos.push(ffi::zend_internal_arg_info {
37+
name: 0 as *const _, // required_num_args
38+
type_: ZendType::empty_from_type(DataType::String, false, false, true) // nullable string
39+
.unwrap_or_else(|| ZendType::empty(false, false)),
40+
default_value: ptr::null(),
41+
});
42+
43+
let load_method_name = CString::new("loadLastCheckpoint").unwrap();
44+
let load_num_args = (load_arg_infos.len() - 1) as u32;
45+
let load_arg_info_ptr = Box::into_raw(load_arg_infos.into_boxed_slice()) as *const _;
46+
47+
let load_method = ffi::zend_function_entry {
48+
fname: load_method_name.as_ptr(),
49+
handler: None,
50+
arg_info: load_arg_info_ptr,
51+
num_args: load_num_args,
52+
flags: (ffi::ZEND_ACC_PUBLIC | ffi::ZEND_ACC_ABSTRACT) as u32,
53+
doc_comment: ptr::null(),
54+
frameless_function_infos: ptr::null(),
55+
};
56+
functions.push(load_method);
57+
58+
// Create arginfo for saveCheckpoint method (1 string parameter, returns void)
59+
let mut save_arg_infos: Vec<ffi::zend_internal_arg_info> = Vec::new();
60+
61+
// First element: metadata (return type void, 1 required arg)
62+
save_arg_infos.push(ffi::zend_internal_arg_info {
63+
name: 1 as *const _, // required_num_args
64+
type_: ZendType::empty_from_type(DataType::Void, false, false, false)
65+
.unwrap_or_else(|| ZendType::empty(false, false)),
66+
default_value: ptr::null(),
67+
});
68+
69+
// Argument 1: $checkpoint (string)
70+
let checkpoint_arg_name = CString::new("checkpoint").unwrap();
71+
save_arg_infos.push(ffi::zend_internal_arg_info {
72+
name: checkpoint_arg_name.into_raw(),
73+
type_: ZendType::empty_from_type(DataType::String, false, false, false)
74+
.unwrap_or_else(|| ZendType::empty(false, false)),
75+
default_value: ptr::null(),
76+
});
77+
78+
let save_method_name = CString::new("saveCheckpoint").unwrap();
79+
let save_num_args = (save_arg_infos.len() - 1) as u32;
80+
let save_arg_info_ptr = Box::into_raw(save_arg_infos.into_boxed_slice()) as *const _;
81+
82+
let save_method = ffi::zend_function_entry {
83+
fname: save_method_name.as_ptr(),
84+
handler: None,
85+
arg_info: save_arg_info_ptr,
86+
num_args: save_num_args,
87+
flags: (ffi::ZEND_ACC_PUBLIC | ffi::ZEND_ACC_ABSTRACT) as u32,
88+
doc_comment: ptr::null(),
89+
frameless_function_infos: ptr::null(),
90+
};
91+
functions.push(save_method);
92+
93+
// Add terminating entry
94+
functions.push(ffi::zend_function_entry {
95+
fname: ptr::null(),
96+
handler: None,
97+
arg_info: ptr::null(),
98+
num_args: 0,
99+
flags: 0,
100+
doc_comment: ptr::null(),
101+
frameless_function_infos: ptr::null(),
102+
});
103+
104+
// Set the functions on the interface
105+
interface_ce.info.internal.builtin_functions = functions.as_ptr();
106+
107+
// Register the interface
108+
let registered = ffi::zend_register_internal_class_ex(
109+
&mut interface_ce as *mut _,
110+
ptr::null_mut(),
111+
);
112+
113+
// Prevent the vectors and strings from being dropped
114+
mem::forget(functions);
115+
mem::forget(load_method_name);
116+
mem::forget(save_method_name);
117+
mem::forget(name);
118+
119+
if registered.is_null() {
120+
eprintln!("Failed to register StreamCheckpointerInterface");
121+
return;
122+
}
123+
124+
// Store the interface reference globally
125+
CHECKPOINTER_INTERFACE = registered;
126+
}
3127

4128
/// Rust wrapper for PHP StreamCheckpointerInterface
5129
/// Provides a clean abstraction over PHP checkpointer objects
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
use ext_php_rs::prelude::*;
2+
use ext_php_rs::ffi;
3+
use ext_php_rs::types::Zval;
4+
use ext_php_rs::convert::{FromZval, IntoZval};
5+
use ext_php_rs::zend::ClassEntry;
6+
use ext_php_rs::flags::{ClassFlags, DataType};
7+
use ext_php_rs::error::Result;
8+
use std::ffi::CString;
9+
use std::mem;
10+
use std::ptr;
11+
12+
// Global pointer to EventInterface
13+
static mut EVENT_INTERFACE: *mut ClassEntry = ptr::null_mut();
14+
15+
// Function to get EventInterface CE
16+
pub fn event_interface_ce() -> &'static ClassEntry {
17+
unsafe {
18+
EVENT_INTERFACE.as_ref().expect("EventInterface not initialized")
19+
}
20+
}
21+
22+
// Unsafe function to register EventInterface
23+
pub unsafe fn register_event_interface() {
24+
// Create and register EventInterface
25+
let mut interface_ce: ffi::zend_class_entry = mem::zeroed();
26+
27+
// Set the interface name
28+
let name = CString::new("DataAccessKit\\Replication\\EventInterface").unwrap();
29+
interface_ce.name = ffi::ext_php_rs_zend_string_init(
30+
name.as_ptr(),
31+
name.as_bytes().len(),
32+
true
33+
);
34+
35+
// Set interface flags
36+
interface_ce.ce_flags = ClassFlags::Interface.bits();
37+
38+
// Register the interface
39+
let registered = ffi::zend_register_internal_class_ex(
40+
&mut interface_ce as *mut _,
41+
ptr::null_mut(),
42+
);
43+
44+
if registered.is_null() {
45+
eprintln!("Failed to register EventInterface");
46+
ffi::ext_php_rs_zend_string_release(interface_ce.name);
47+
return;
48+
}
49+
50+
// Store the EventInterface reference globally
51+
EVENT_INTERFACE = registered;
52+
53+
// Add constants to the interface
54+
let insert_const = CString::new("INSERT").unwrap();
55+
let mut insert_val = Zval::new();
56+
insert_val.set_string("INSERT", true).unwrap();
57+
ffi::zend_declare_class_constant(
58+
registered,
59+
insert_const.as_ptr(),
60+
insert_const.as_bytes().len(),
61+
Box::into_raw(Box::new(insert_val)),
62+
);
63+
64+
let update_const = CString::new("UPDATE").unwrap();
65+
let mut update_val = Zval::new();
66+
update_val.set_string("UPDATE", true).unwrap();
67+
ffi::zend_declare_class_constant(
68+
registered,
69+
update_const.as_ptr(),
70+
update_const.as_bytes().len(),
71+
Box::into_raw(Box::new(update_val)),
72+
);
73+
74+
let delete_const = CString::new("DELETE").unwrap();
75+
let mut delete_val = Zval::new();
76+
delete_val.set_string("DELETE", true).unwrap();
77+
ffi::zend_declare_class_constant(
78+
registered,
79+
delete_const.as_ptr(),
80+
delete_const.as_bytes().len(),
81+
Box::into_raw(Box::new(delete_val)),
82+
);
83+
}
84+
85+
// Wrapper for Zval that implements Clone
86+
pub struct Mixed(Zval);
87+
88+
impl Clone for Mixed {
89+
fn clone(&self) -> Self {
90+
Mixed(self.0.shallow_clone())
91+
}
92+
}
93+
94+
impl Mixed {
95+
pub fn new(val: &Zval) -> Self {
96+
Mixed(val.shallow_clone())
97+
}
98+
}
99+
100+
impl IntoZval for Mixed {
101+
const TYPE: DataType = DataType::Mixed;
102+
const NULLABLE: bool = true;
103+
104+
fn set_zval(self, zv: &mut Zval, _persistent: bool) -> Result<()> {
105+
*zv = self.0;
106+
Ok(())
107+
}
108+
}
109+
110+
impl<'a> FromZval<'a> for Mixed {
111+
const TYPE: DataType = DataType::Mixed;
112+
113+
fn from_zval(zval: &'a Zval) -> Option<Self> {
114+
Some(Mixed(zval.shallow_clone()))
115+
}
116+
}
117+
118+
#[php_class]
119+
#[php(name = "DataAccessKit\\Replication\\InsertEvent")]
120+
#[php(implements(ce = event_interface_ce, stub = "DataAccessKit\\Replication\\EventInterface"))]
121+
pub struct InsertEvent {
122+
#[php(prop, name = "type")]
123+
r#type: String,
124+
#[php(prop)]
125+
timestamp: i64,
126+
#[php(prop)]
127+
checkpoint: String,
128+
#[php(prop)]
129+
schema: String,
130+
#[php(prop)]
131+
table: String,
132+
#[php(prop)]
133+
after: Mixed,
134+
}
135+
136+
#[php_impl]
137+
impl InsertEvent {
138+
pub fn __construct(
139+
r#type: String,
140+
timestamp: i64,
141+
checkpoint: String,
142+
schema: String,
143+
table: String,
144+
after: &Zval,
145+
) -> PhpResult<Self> {
146+
Ok(InsertEvent {
147+
r#type,
148+
timestamp,
149+
checkpoint,
150+
schema,
151+
table,
152+
after: Mixed::new(after),
153+
})
154+
}
155+
}
156+
157+
#[php_class]
158+
#[php(name = "DataAccessKit\\Replication\\UpdateEvent")]
159+
#[php(implements(ce = event_interface_ce, stub = "DataAccessKit\\Replication\\EventInterface"))]
160+
pub struct UpdateEvent {
161+
#[php(prop, name = "type")]
162+
r#type: String,
163+
#[php(prop)]
164+
timestamp: i64,
165+
#[php(prop)]
166+
checkpoint: String,
167+
#[php(prop)]
168+
schema: String,
169+
#[php(prop)]
170+
table: String,
171+
#[php(prop)]
172+
before: Mixed,
173+
#[php(prop)]
174+
after: Mixed,
175+
}
176+
177+
#[php_impl]
178+
impl UpdateEvent {
179+
pub fn __construct(
180+
r#type: String,
181+
timestamp: i64,
182+
checkpoint: String,
183+
schema: String,
184+
table: String,
185+
before: &Zval,
186+
after: &Zval,
187+
) -> PhpResult<Self> {
188+
Ok(UpdateEvent {
189+
r#type,
190+
timestamp,
191+
checkpoint,
192+
schema,
193+
table,
194+
before: Mixed::new(before),
195+
after: Mixed::new(after),
196+
})
197+
}
198+
}
199+
200+
#[php_class]
201+
#[php(name = "DataAccessKit\\Replication\\DeleteEvent")]
202+
#[php(implements(ce = event_interface_ce, stub = "DataAccessKit\\Replication\\EventInterface"))]
203+
pub struct DeleteEvent {
204+
#[php(prop, name = "type")]
205+
r#type: String,
206+
#[php(prop)]
207+
timestamp: i64,
208+
#[php(prop)]
209+
checkpoint: String,
210+
#[php(prop)]
211+
schema: String,
212+
#[php(prop)]
213+
table: String,
214+
#[php(prop)]
215+
before: Mixed,
216+
}
217+
218+
#[php_impl]
219+
impl DeleteEvent {
220+
pub fn __construct(
221+
r#type: String,
222+
timestamp: i64,
223+
checkpoint: String,
224+
schema: String,
225+
table: String,
226+
before: &Zval,
227+
) -> PhpResult<Self> {
228+
Ok(DeleteEvent {
229+
r#type,
230+
timestamp,
231+
checkpoint,
232+
schema,
233+
table,
234+
before: Mixed::new(before),
235+
})
236+
}
237+
}

0 commit comments

Comments
 (0)