diff --git a/src/php/access/ChainAccess.php b/src/php/access/ChainAccess.php
new file mode 100644
index 0000000..cd3d1cd
--- /dev/null
+++ b/src/php/access/ChainAccess.php
@@ -0,0 +1,150 @@
+<?php
+namespace nur\sery\wip\php\access;
+
+use nulib\cl;
+use nulib\StateException;
+use ReflectionClass;
+use ReflectionException;
+
+class ChainAccess extends AbstractAccess {
+  const ACCESS_AUTO = 0, ACCESS_KEY = 1, ACCESS_PROPERTY = 2;
+
+  function __construct(IAccess $access, $key, ?array $params=null) {
+    parent::__construct();
+    $this->access = $access;
+    $this->key = $key;
+    $this->accessType = $params["access_type"] ?? self::ACCESS_AUTO;
+  }
+
+  protected IAccess $access;
+
+  /** @var int|string|array */
+  protected $key;
+
+  protected int $accessType;
+
+  protected function _accessType($access, $key): int {
+    $accessType = $this->accessType;
+    if ($accessType === self::ACCESS_AUTO) {
+      if (is_object($access) && is_string($key)) {
+        $accessType = self::ACCESS_PROPERTY;
+      } else {
+        $accessType = self::ACCESS_KEY;
+      }
+    }
+    return $accessType;
+  }
+
+  protected function _has(): bool {
+    $src = $this->access->get();
+    $key = $this->key;
+    $accessType = $this->_accessType($src, $key);
+    if ($accessType === self::ACCESS_KEY) {
+      return cl::phas($src, $key);
+    } elseif ($accessType === self::ACCESS_PROPERTY) {
+      $class = new ReflectionClass($src);
+      return $class->hasProperty($key) || property_exists($src, $key);
+    } else {
+      throw StateException::unexpected_state();
+    }
+  }
+
+  protected function _get($default=null) {
+    $src = $this->access->get();
+    $key = $this->key;
+    $accessType = $this->_accessType($src, $key);
+    if ($accessType === self::ACCESS_KEY) {
+      return cl::pget($src, $key);
+    } elseif ($accessType === self::ACCESS_PROPERTY) {
+      $class = new ReflectionClass($src);
+      try {
+        $property = $class->getProperty($key);
+        $property->setAccessible(true);
+      } catch (ReflectionException $e) {
+        $property = null;
+      }
+      if ($property !== null) {
+        return $property->getValue($src);
+      } elseif (property_exists($src, $key)) {
+        return $src->$key;
+      } else {
+        return $default;
+      }
+    } else {
+      throw StateException::unexpected_state();
+    }
+  }
+
+  protected function _pset(object $dest, $name, $value): void {
+    $class = new ReflectionClass($dest);
+    try {
+      $property = $class->getProperty($name);
+      $property->setAccessible(true);
+    } catch (ReflectionException $e) {
+      $property = null;
+    }
+    if ($property !== null) {
+      $property->setValue($dest, $value);
+    } else {
+      $dest->$name = $value;
+    }
+  }
+
+  function isAllowEmpty(): bool {
+    return $this->access->isAllowEmpty();
+  }
+
+  function exists(): bool {
+    if ($this->key === null) return false;
+    if (!$this->access->exists()) return false;
+    return $this->_has();
+  }
+
+  function available(): bool {
+    if ($this->key === null) return false;
+    if (!$this->access->available()) return false;
+    if (!$this->_has()) return false;
+    return $this->isAllowEmpty() || $this->_get() !== "";
+  }
+
+  function get($default=null) {
+    return $this->_get($default);
+  }
+
+  function set($value): void {
+    $dest = $this->access->get();
+    $key = $this->key;
+    $accessType = $this->_accessType($dest, $key);
+    if ($accessType === self::ACCESS_KEY) {
+      cl::pset($dest, $key, $value);
+      $this->access->set($dest);
+    } elseif ($accessType === self::ACCESS_PROPERTY) {
+      $this->_pset($dest, $key, $value);
+    } else {
+      throw StateException::unexpected_state();
+    }
+  }
+
+  function del(): void {
+    $dest = $this->access->get();
+    $key = $this->key;
+    $accessType = $this->_accessType($dest, $key);
+    if ($accessType === self::ACCESS_KEY) {
+      cl::pdel($dest, $key);
+      $this->access->set($dest);
+    } elseif ($accessType === self::ACCESS_PROPERTY) {
+      $this->_pset($dest, $key, null);
+    } else {
+      throw StateException::unexpected_state();
+    }
+  }
+
+  function addKey($key, ?array $params=null): IAccess {
+    $accessType = $params["access_type"] ?? self::ACCESS_AUTO;
+    if ($accessType === self::ACCESS_KEY && $accessType === $this->accessType) {
+      return new ChainAccess($this->access, cl::merge($this->key, $key));
+    } else {
+      return new ChainAccess($this, $key);
+    }
+  }
+}
diff --git a/src/php/access/ChainKeyAccess.php b/src/php/access/ChainKeyAccess.php
deleted file mode 100644
index f2460f8..0000000
--- a/src/php/access/ChainKeyAccess.php
+++ /dev/null
@@ -1,57 +0,0 @@
-<?php
-namespace nur\sery\wip\php\access;
-
-use nulib\cl;
-
-class ChainKeyAccess extends AbstractAccess {
-  function __construct(IAccess $access, $key) {
-    parent::__construct();
-    $this->access = $access;
-    $this->key = $key;
-  }
-
-  protected IAccess $access;
-
-  /** @var int|string|array */
-  protected $key;
-
-  function isAllowEmpty(): bool {
-    return $this->access->isAllowEmpty();
-  }
-
-  function exists(): bool {
-    $key = $this->key;
-    if ($key === null) return false;
-    if (!$this->access->exists()) return false;
-    return cl::phas($this->access->get(), $key);
-  }
-
-  function available(): bool {
-    $key = $this->key;
-    if ($key === null) return false;
-    if (!$this->access->available()) return false;
-    $array = $this->access->get();
-    if (!cl::phas($array, $key)) return false;
-    return $this->isAllowEmpty() || cl::pget($array, $key) !== "";
-  }
-
-  function get($default=null) {
-    return cl::pget($this->access->get($default), $this->key);
-  }
-
-  function set($value): void {
-    $array = $this->access->get();
-    cl::pset($array, $this->key, $value);
-    $this->access->set($array);
-  }
-
-  function del(): void {
-    $array = $this->access->get();
-    cl::pdel($array, $this->key);
-    $this->access->set($array);
-  }
-
-  function addKey($key): IAccess {
-    return new ChainKeyAccess($this->access, cl::merge($this->key, $key));
-  }
-}
diff --git a/src/php/access/PropertyAccess.php b/src/php/access/PropertyAccess.php
index ec823c1..832ea88 100644
--- a/src/php/access/PropertyAccess.php
+++ b/src/php/access/PropertyAccess.php
@@ -91,6 +91,6 @@ class PropertyAccess extends AbstractAccess {
   }
 
   function addKey($key): IAccess {
-    return new ChainKeyAccess($this, $key);
+    return new ChainAccess($this, $key);
   }
 }
diff --git a/src/php/access/ShadowAccess.php b/src/php/access/ShadowAccess.php
index ab0e33a..e4f1211 100644
--- a/src/php/access/ShadowAccess.php
+++ b/src/php/access/ShadowAccess.php
@@ -55,6 +55,6 @@ class ShadowAccess extends AbstractAccess {
   }
 
   function addKey($key): IAccess {
-    return new ChainKeyAccess($this, $key);
+    return new ChainAccess($this, $key);
   }
 }