Skip to content

Redesign the way to bind Class and Interface #175

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 17 additions & 14 deletions examples/http-client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,24 @@
// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.

use crate::{errors::HttpClientError, request::REQUEST_BUILDER_CLASS};
use crate::{errors::HttpClientError, request::RequestBuilderClass};
use phper::{
alloc::ToRefOwned,
classes::{ClassEntity, StaticStateClass, Visibility},
classes::{ClassEntity, StateClass, Visibility},
functions::Argument,
};
use reqwest::blocking::{Client, ClientBuilder};
use std::{convert::Infallible, mem::take, time::Duration};

pub type ClientBuilderClass = StateClass<ClientBuilder>;

pub type ClientClass = StateClass<Option<Client>>;

const HTTP_CLIENT_BUILDER_CLASS_NAME: &str = "HttpClient\\HttpClientBuilder";

const HTTP_CLIENT_CLASS_NAME: &str = "HttpClient\\HttpClient";

static HTTP_CLIENT_CLASS: StaticStateClass<Option<Client>> = StaticStateClass::null();

pub fn make_client_builder_class() -> ClassEntity<ClientBuilder> {
pub fn make_client_builder_class(client_class: ClientClass) -> ClassEntity<ClientBuilder> {
// `new_with_default_state_constructor` means initialize the state of
// `ClientBuilder` as `Default::default`.
let mut class = ClassEntity::new_with_default_state_constructor(HTTP_CLIENT_BUILDER_CLASS_NAME);
Expand Down Expand Up @@ -52,44 +54,45 @@ pub fn make_client_builder_class() -> ClassEntity<ClientBuilder> {

// Inner call the `ClientBuilder::build`, and wrap the result `Client` in
// Object.
class.add_method("build", Visibility::Public, |this, _arguments| {
class.add_method("build", Visibility::Public, move |this, _arguments| {
let state = take(this.as_mut_state());
let client = ClientBuilder::build(state).map_err(HttpClientError::Reqwest)?;
let mut object = HTTP_CLIENT_CLASS.init_object()?;
let mut object = client_class.init_object()?;
*object.as_mut_state() = Some(client);
Ok::<_, phper::Error>(object)
});

class
}

pub fn make_client_class() -> ClassEntity<Option<Client>> {
pub fn make_client_class(
request_builder_class: RequestBuilderClass,
) -> ClassEntity<Option<Client>> {
let mut class =
ClassEntity::<Option<Client>>::new_with_default_state_constructor(HTTP_CLIENT_CLASS_NAME);

class.bind(&HTTP_CLIENT_CLASS);

class.add_method("__construct", Visibility::Private, |_, _| {
Ok::<_, Infallible>(())
});

let request_build_class_ = request_builder_class.clone();
class
.add_method("get", Visibility::Public, |this, arguments| {
.add_method("get", Visibility::Public, move |this, arguments| {
let url = arguments[0].expect_z_str()?.to_str().unwrap();
let client = this.as_state().as_ref().unwrap();
let request_builder = client.get(url);
let mut object = REQUEST_BUILDER_CLASS.init_object()?;
let mut object = request_build_class_.init_object()?;
*object.as_mut_state() = Some(request_builder);
Ok::<_, phper::Error>(object)
})
.argument(Argument::by_val("url"));

class
.add_method("post", Visibility::Public, |this, arguments| {
.add_method("post", Visibility::Public, move |this, arguments| {
let url = arguments[0].expect_z_str()?.to_str().unwrap();
let client = this.as_state().as_ref().unwrap();
let request_builder = client.post(url);
let mut object = REQUEST_BUILDER_CLASS.init_object()?;
let mut object = request_builder_class.init_object()?;
*object.as_mut_state() = Some(request_builder);
Ok::<_, phper::Error>(object)
})
Expand Down
8 changes: 4 additions & 4 deletions examples/http-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ pub fn get_module() -> Module {
);

module.add_class(make_exception_class());
module.add_class(make_client_class());
module.add_class(make_client_builder_class());
module.add_class(make_request_builder_class());
module.add_class(make_response_class());
let response_class = module.add_class(make_response_class());
let request_builder_class = module.add_class(make_request_builder_class(response_class));
let client_class = module.add_class(make_client_class(request_builder_class));
module.add_class(make_client_builder_class(client_class));

module
}
19 changes: 9 additions & 10 deletions examples/http-client/src/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,30 @@
// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.

use crate::{errors::HttpClientError, response::RESPONSE_CLASS};
use phper::classes::{ClassEntity, StaticStateClass, Visibility};
use crate::{errors::HttpClientError, response::ResponseClass};
use phper::classes::{ClassEntity, StateClass, Visibility};
use reqwest::blocking::RequestBuilder;
use std::{convert::Infallible, mem::take};

pub const REQUEST_BUILDER_CLASS_NAME: &str = "HttpClient\\RequestBuilder";
pub type RequestBuilderClass = StateClass<Option<RequestBuilder>>;

pub static REQUEST_BUILDER_CLASS: StaticStateClass<Option<RequestBuilder>> =
StaticStateClass::null();
pub const REQUEST_BUILDER_CLASS_NAME: &str = "HttpClient\\RequestBuilder";

pub fn make_request_builder_class() -> ClassEntity<Option<RequestBuilder>> {
pub fn make_request_builder_class(
response_class: ResponseClass,
) -> ClassEntity<Option<RequestBuilder>> {
let mut class = ClassEntity::<Option<RequestBuilder>>::new_with_default_state_constructor(
REQUEST_BUILDER_CLASS_NAME,
);

class.bind(&REQUEST_BUILDER_CLASS);

class.add_method("__construct", Visibility::Private, |_, _| {
Ok::<_, Infallible>(())
});

class.add_method("send", Visibility::Public, |this, _arguments| {
class.add_method("send", Visibility::Public, move |this, _arguments| {
let state = take(this.as_mut_state());
let response = state.unwrap().send().map_err(HttpClientError::Reqwest)?;
let mut object = RESPONSE_CLASS.new_object([])?;
let mut object = response_class.new_object([])?;
*object.as_mut_state() = Some(response);
Ok::<_, phper::Error>(object)
});
Expand Down
8 changes: 3 additions & 5 deletions examples/http-client/src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,20 @@
use crate::errors::HttpClientError;
use phper::{
arrays::{InsertKey, ZArray},
classes::{ClassEntity, StaticStateClass, Visibility},
classes::{ClassEntity, StateClass, Visibility},
values::ZVal,
};
use reqwest::blocking::Response;
use std::mem::take;

pub const RESPONSE_CLASS_NAME: &str = "HttpClient\\Response";
pub type ResponseClass = StateClass<Option<Response>>;

pub static RESPONSE_CLASS: StaticStateClass<Option<Response>> = StaticStateClass::null();
pub const RESPONSE_CLASS_NAME: &str = "HttpClient\\Response";

pub fn make_response_class() -> ClassEntity<Option<Response>> {
let mut class =
ClassEntity::<Option<Response>>::new_with_default_state_constructor(RESPONSE_CLASS_NAME);

class.bind(&RESPONSE_CLASS);

class.add_method("body", Visibility::Public, |this, _arguments| {
let response = take(this.as_mut_state());
let response = response.ok_or(HttpClientError::ResponseHadRead)?;
Expand Down
6 changes: 3 additions & 3 deletions examples/http-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ pub fn get_module() -> Module {

// Register classes.
module.add_class(make_exception_class());
module.add_class(make_server_class());
module.add_class(make_request_class());
module.add_class(make_response_class());
let request_class = module.add_class(make_request_class());
let response_class = module.add_class(make_response_class());
module.add_class(make_server_class(request_class, response_class));

module
}
15 changes: 3 additions & 12 deletions examples/http-server/src/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,18 @@

use phper::{
arrays::ZArray,
classes::{ClassEntity, StaticStateClass, Visibility},
objects::StateObject,
classes::{ClassEntity, StateClass, Visibility},
};
use std::convert::Infallible;

pub const HTTP_REQUEST_CLASS_NAME: &str = "HttpServer\\HttpRequest";
pub type RequestClass = StateClass<()>;

pub static HTTP_REQUEST_CLASS: StaticStateClass<()> = StaticStateClass::null();
pub const HTTP_REQUEST_CLASS_NAME: &str = "HttpServer\\HttpRequest";

/// Register the class `HttpServer\HttpRequest` by `ClassEntity`.
pub fn make_request_class() -> ClassEntity<()> {
let mut class = ClassEntity::new(HTTP_REQUEST_CLASS_NAME);

// The state class will be initialized after class registered.
class.bind(&HTTP_REQUEST_CLASS);

// Register the http headers field with public visibility.
class.add_property("headers", Visibility::Public, ());

Expand All @@ -41,8 +37,3 @@ pub fn make_request_class() -> ClassEntity<()> {

class
}

/// Instantiate the object with class `HttpServer\HttpRequest`.
pub fn new_request_object() -> phper::Result<StateObject<()>> {
HTTP_REQUEST_CLASS.new_object([])
}
15 changes: 3 additions & 12 deletions examples/http-server/src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,19 @@ use axum::{
http::{HeaderName, HeaderValue, Response},
};
use phper::{
classes::{ClassEntity, StaticStateClass, Visibility},
classes::{ClassEntity, StateClass, Visibility},
functions::Argument,
objects::StateObject,
};

pub const HTTP_RESPONSE_CLASS_NAME: &str = "HttpServer\\HttpResponse";
pub type ResponseClass = StateClass<Response<Body>>;

pub static HTTP_RESPONSE_CLASS: StaticStateClass<Response<Body>> = StaticStateClass::null();
pub const HTTP_RESPONSE_CLASS_NAME: &str = "HttpServer\\HttpResponse";

/// Register the class `HttpServer\HttpResponse` by `ClassEntity`, with the
/// inner state `Response<Body>`.
pub fn make_response_class() -> ClassEntity<Response<Body>> {
let mut class = ClassEntity::new_with_default_state_constructor(HTTP_RESPONSE_CLASS_NAME);

// The state class will be initialized after class registered.
class.bind(&HTTP_RESPONSE_CLASS);

// Register the header method with public visibility, accept `name` and `value`
// parameters.
class
Expand Down Expand Up @@ -62,8 +58,3 @@ pub fn make_response_class() -> ClassEntity<Response<Body>> {

class
}

/// Instantiate the object with class `HttpServer\HttpResponse`.
pub fn new_response_object() -> phper::Result<StateObject<Response<Body>>> {
HTTP_RESPONSE_CLASS.new_object([])
}
70 changes: 43 additions & 27 deletions examples/http-server/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.

use crate::{errors::HttpServerError, request::new_request_object, response::new_response_object};
use crate::{errors::HttpServerError, request::RequestClass, response::ResponseClass};
use axum::{
body::{self, Body},
http::{Request, Response, StatusCode},
Expand All @@ -31,10 +31,14 @@ const HTTP_SERVER_CLASS_NAME: &str = "HttpServer\\HttpServer";
thread_local! {
// The map store the on request handlers, indexed by `HttpServer\HttpServer` object id.
static ON_REQUEST_HANDLERS: RefCell<HashMap<u32, ZVal>> = Default::default();

static CLASSES_MAP: RefCell<HashMap<u32, (RequestClass, ResponseClass)>> = Default::default();
}

/// Register the class `HttpServer\HttpServer` by `ClassEntity`.
pub fn make_server_class() -> ClassEntity<()> {
pub fn make_server_class(
request_class: RequestClass, response_class: ResponseClass,
) -> ClassEntity<()> {
let mut class = ClassEntity::new(HTTP_SERVER_CLASS_NAME);

// Register the server host field with public visibility.
Expand All @@ -46,13 +50,21 @@ pub fn make_server_class() -> ClassEntity<()> {
// Register the constructor method with public visibility, accept host and port
// arguments, initialize the host and port member properties.
class
.add_method("__construct", Visibility::Public, |this, arguments| {
.add_method("__construct", Visibility::Public, move |this, arguments| {
let host = arguments[0].expect_z_str()?;
let port = arguments[1].expect_long()?;

this.set_property("host", host.to_owned());
this.set_property("port", port);

let (request_class, response_class) = (request_class.clone(), response_class.clone());

CLASSES_MAP.with(move |classes_map| {
classes_map
.borrow_mut()
.insert(this.handle(), (request_class, response_class));
});

Ok::<_, phper::Error>(())
})
.arguments([Argument::by_val("host"), Argument::by_val("port")]);
Expand Down Expand Up @@ -98,40 +110,44 @@ pub fn make_server_class() -> ClassEntity<()> {
.await
.map_err(HttpServerError::new)?;

// Create PHP `HttpServer\HttpRequest` object.
let mut request = new_request_object()?;
CLASSES_MAP.with(|classes_map| {
let (request_class, response_class) = &classes_map.borrow()[&handle];

// Create PHP `HttpServer\HttpRequest` object.
let mut request = request_class.new_object([])?;

// Inject headers from Rust request object to PHP request object.
let request_headers =
request.get_mut_property("headers").expect_mut_z_arr()?;
for (key, value) in parts.headers {
if let Some(key) = key {
request_headers.insert(key.as_str(), value.as_bytes());
// Inject headers from Rust request object to PHP request object.
let request_headers =
request.get_mut_property("headers").expect_mut_z_arr()?;
for (key, value) in parts.headers {
if let Some(key) = key {
request_headers.insert(key.as_str(), value.as_bytes());
}
}
}

// Inject body content from Rust request object to PHP request object.
request.set_property("data", &*body);
// Inject body content from Rust request object to PHP request object.
request.set_property("data", &*body);

let request_val = ZVal::from(request);
let request_val = ZVal::from(request);

// Create PHP `HttpServer\HttpResponse` object.
let mut response = new_response_object()?;
// Create PHP `HttpServer\HttpResponse` object.
let mut response = response_class.new_object([])?;

let response_val = ZVal::from(response.to_ref_owned());
let response_val = ZVal::from(response.to_ref_owned());

ON_REQUEST_HANDLERS.with(|handlers| {
// Get the on request handlers by object id.
let mut handlers = handlers.borrow_mut();
let handler = handlers.get_mut(&handle).unwrap();
ON_REQUEST_HANDLERS.with(|handlers| {
// Get the on request handlers by object id.
let mut handlers = handlers.borrow_mut();
let handler = handlers.get_mut(&handle).unwrap();

// Call the PHP on request handler.
handler.call([request_val, response_val])?;
// Call the PHP on request handler.
handler.call([request_val, response_val])?;

// Get the inner state.
let response = response.into_state().unwrap();
// Get the inner state.
let response = response.into_state().unwrap();

Ok::<Response<Body>, phper::Error>(response)
Ok::<Response<Body>, phper::Error>(response)
})
})
};
match fut.await {
Expand Down
Loading
Loading