
# 공유 객체

PHP 객체를 참조로 JavaScript에 전달해 속성을 읽고 메서드를 호출하거나 PHP의 변경 사항을 확인하는 기능은 **아직 지원되지 않습니다**. 이 문제는 [이슈 #18](https://github.com/inline0/phasis/issues/18)에서 추적 중입니다. 이 페이지에서는 현재 동작과 대신 사용할 수 있는 패턴을 설명합니다.

## 현재 동작

`setGlobal()`은 [값 변환](https://phasis.inline0.dev/ko/docs/interop/values)의 규칙에 따라 값을 변환합니다. 호출할 수 없는 PHP 객체는 변환 규칙이 없으므로 전역 객체는 `undefined`입니다:

```php
class Counter {
    public int $value = 0;
    public function increment(): void { $this->value++; }
}

$counter = new Counter();
$engine->setGlobal('counter', $counter);

$engine->eval('typeof counter');
// "undefined"

$engine->eval('counter.increment()');
// throws Phasis\Exceptions\TypeError:
// Cannot read properties of undefined (reading 'increment')
```

`stdClass`, 엔터티와 모델, 서비스 컨테이너, 제너레이터 및 리소스에도 동일하게 적용됩니다. 호출 가능한 객체에는 두 가지 예외가 있습니다:

- `__invoke()`이 있는 객체는 이를 호출하는 JS 함수가 됩니다.
- `[$counter, 'increment']` 같은 호출 가능한 배열은 해당 메서드를 호출하는 JS 함수가 됩니다.

객체를 노출하는 `PhpBridge`이나 다른 래퍼 클래스는 없습니다. `src/Interop/`에는 값 변환기만 포함됩니다.

## 메서드를 클로저로 노출

클로저는 객체를 캡처할 수 있으므로 JS이 함수를 호출하면 메서드는 원래 PHP 인스턴스에서 실행됩니다:

```php
$counter = new Counter();
$engine->setGlobal('increment', fn () => $counter->increment());
$engine->setGlobal('count', fn () => $counter->value);

$engine->eval('increment(); increment(); count()');
// 2

echo $counter->value;
// 2
```

호출 가능한 배열을 사용하면 클로저 없이도 같은 작업을 수행할 수 있습니다:

```php
$engine->setGlobal('increment', [$counter, 'increment']);
```

## 배열에 함께 담기

연관 배열은 JS 객체가 되고 내부 클로저는 함수가 됩니다. 이렇게 하면 JS에 객체 형태의 API를 제공해 메서드가 PHP 인스턴스에서 동작하게 할 수 있습니다:

```php
class User {
    public function __construct(
        public string $name,
        public int $age,
        private string $secret,
    ) {}

    public function greet(): string {
        return "Hi, I'm $this->name";
    }
}

$user = new User('Ada', 36, 'topsecret');

$engine->setGlobal('user', [
    'name'   => $user->name,
    'greet'  => fn () => $user->greet(),
    'setAge' => function (int $age) use ($user): void { $user->age = $age; },
]);

$engine->eval('user.greet()');
// "Hi, I'm Ada"

$engine->eval('user.setAge(37)');
echo $user->age;
// 37
```

배열에 넣은 항목만 JS에 표시되므로 `$secret` 같은 비공개 상태는 PHP에 그대로 남습니다.

데이터 필드는 `setGlobal()`이 실행될 때 복사됩니다. 예제에서 `user.name`은 JS의 문자열입니다. 여기에 값을 할당하면 JS 복사본만 바뀌고 `$user->name`은 바뀌지 않습니다. 모든 쓰기 작업은 클로저를 통해 처리하세요.

## 데이터를 내보내고 결과를 돌려받기

데이터 형태의 객체는 입력 시 배열로 변환하고, 출력 시 결과를 읽으세요:

```php
$post = ['title' => 'Draft', 'tags' => ['php']];

$engine->setGlobal('post', $post);
$engine->eval('function edit(p) { p.title = "Edited from JS"; return p; }');

$post = $engine->call('edit', $post);
echo $post['title'];
// Edited from JS
```

`get_object_vars($object)` 또는 `toArray()` 메서드를 사용하면 객체의 배열을 얻을 수 있습니다. 그런 다음 반환된 배열을 PHP에서 객체, 엔터티 또는 모델에 적용하세요.

## 주의 사항

- 이러한 클로저 내부에서 발생한 예외는 현재 JS `try` / `catch`에서 잡을 수 없으며, `eval()`의 호출자에게 전달됩니다([이슈 #20](https://github.com/inline0/phasis/issues/20)). [호스트 함수](https://phasis.inline0.dev/ko/docs/interop/host-functions#throwing-back-into-js)를 참조하세요.
- `setGlobal()`에 등록된 클로저는 엔진이 전역 객체를 보유하는 동안 캡처한 객체를 계속 유지합니다.
