From f01732193f91eaa82627e5c20dcba67da84d658d Mon Sep 17 00:00:00 2001 From: agis Date: Fri, 11 Sep 2026 10:34:33 +0700 Subject: [PATCH] =?UTF-8?q?refactor(support):=20break=20SCC-1=20=E2=80=94?= =?UTF-8?q?=20decouple=20Support=20from=20Http=20&=20Container=20(task=203?= =?UTF-8?q?.3a)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wave 3.3 (slice: SCC-1 break saja; Collection→sig L13 ditunda ke task terpisah). Menghapus dua satu-satunya edge konkret Support→core yang menjerat 11-node SCC-1: Support→Container: - CapsuleManagerTrait: typehint `Contracts\Container\Container` (verbatim L13), drop default `?: new Container`. Database/Queue Capsule\Manager instansiasi default konkret sendiri (`$container ?: new Container`). - Container konkret kini `implements Contracts\Container\Container` (extends PSR); `factory()` param dilebarkan (drop typehint `string`) supaya kompatibel kontrak. Support→Http: - `Support\Facades\Response` jadi Facade accessor murni (→ Contracts\Routing\ ResponseFactory), tak lagi `use Illuminate\Http\*` / `new` konkret. - Logika response dipindah ke `Routing\ResponseFactory implements Contracts\Routing\ResponseFactory` (trim: make/view/json/jsonp/stream/download — method yang fork punya; bukan port penuh L13). 'view' di-resolve lazily dari container (perilaku facade lama) supaya make()/json() jalan tanpa 'view' bound. - Binding di `RoutingServiceProvider::registerResponseFactory()`. Verifikasi: grep edge Support→Http/Container konkret = 0; full suite hijau (1640); ratchet lolos. Test: SupportFacadeResponseTest diadaptasi (facade+container) + 2 test baru (make tanpa view, view lazy-resolve). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/Illuminate/Container/Container.php | 6 +- .../Contracts/Routing/ResponseFactory.php | 71 +++++++++++ src/Illuminate/Database/Capsule/Manager.php | 2 +- src/Illuminate/Queue/Capsule/Manager.php | 2 +- src/Illuminate/Routing/ResponseFactory.php | 80 ++++++++++++ .../Routing/RoutingServiceProvider.php | 15 +++ src/Illuminate/Support/Facades/Response.php | 119 +++--------------- .../Support/Traits/CapsuleManagerTrait.php | 118 ++++++++--------- tests/Support/SupportFacadeResponseTest.php | 37 ++++++ 9 files changed, 284 insertions(+), 166 deletions(-) create mode 100644 src/Illuminate/Contracts/Routing/ResponseFactory.php create mode 100644 src/Illuminate/Routing/ResponseFactory.php diff --git a/src/Illuminate/Container/Container.php b/src/Illuminate/Container/Container.php index b8627ac6b..61951bada 100755 --- a/src/Illuminate/Container/Container.php +++ b/src/Illuminate/Container/Container.php @@ -7,12 +7,12 @@ use Illuminate\Support\Reflector; use Illuminate\Support\Util; use LogicException; -use Psr\Container\ContainerInterface; +use Illuminate\Contracts\Container\Container as ContainerContract; use ReflectionClass; use ReflectionException; use ReflectionParameter; -class Container implements ArrayAccess, ContainerInterface { +class Container implements ArrayAccess, ContainerContract { private static ?Container $instance; @@ -699,7 +699,7 @@ public function call($callback, array $parameters = [], $defaultMethod = null) * * @return \Closure */ - public function factory(string $abstract): Closure + public function factory($abstract): Closure { return function () use ($abstract) { return $this->make($abstract); diff --git a/src/Illuminate/Contracts/Routing/ResponseFactory.php b/src/Illuminate/Contracts/Routing/ResponseFactory.php new file mode 100644 index 000000000..09ee8411a --- /dev/null +++ b/src/Illuminate/Contracts/Routing/ResponseFactory.php @@ -0,0 +1,71 @@ +setupContainer($container); + $this->setupContainer($container ?: new Container); // Once we have the container setup, we will setup the default configuration // options in the container "config" binding. This will make the database diff --git a/src/Illuminate/Queue/Capsule/Manager.php b/src/Illuminate/Queue/Capsule/Manager.php index de6c743c4..f1decc481 100644 --- a/src/Illuminate/Queue/Capsule/Manager.php +++ b/src/Illuminate/Queue/Capsule/Manager.php @@ -24,7 +24,7 @@ class Manager { */ public function __construct(?Container $container = null) { - $this->setupContainer($container); + $this->setupContainer($container ?: new Container); // Once we have the container setup, we will setup the default configuration // options in the container "config" bindings. This just makes this queue diff --git a/src/Illuminate/Routing/ResponseFactory.php b/src/Illuminate/Routing/ResponseFactory.php new file mode 100644 index 000000000..226db8d4e --- /dev/null +++ b/src/Illuminate/Routing/ResponseFactory.php @@ -0,0 +1,80 @@ +container = $container; + } + + public function make($content = '', $status = 200, array $headers = []) + { + return new Response($content, $status, $headers); + } + + public function view($view, $data = [], $status = 200, array $headers = []) + { + return $this->make($this->container['view']->make($view, $data), $status, $headers); + } + + public function json($data = [], $status = 200, array $headers = [], $options = 0) + { + if ($data instanceof Arrayable) + { + $data = $data->toArray(); + } + + return new JsonResponse($data, $status, $headers, $options); + } + + public function jsonp($callback, $data = [], $status = 200, array $headers = [], $options = 0) + { + return $this->json($data, $status, $headers, $options)->setCallback($callback); + } + + public function stream($callback, $status = 200, array $headers = []) + { + return new StreamedResponse($callback, $status, $headers); + } + + public function download($file, $name = null, array $headers = [], $disposition = 'attachment') + { + $response = new BinaryFileResponse($file, 200, $headers, true, $disposition); + + if ( ! is_null($name)) + { + return $response->setContentDisposition($disposition, $name, str_replace('%', '', Str::ascii($name))); + } + + return $response; + } + +} diff --git a/src/Illuminate/Routing/RoutingServiceProvider.php b/src/Illuminate/Routing/RoutingServiceProvider.php index f08b9f262..9d1d078f9 100755 --- a/src/Illuminate/Routing/RoutingServiceProvider.php +++ b/src/Illuminate/Routing/RoutingServiceProvider.php @@ -16,6 +16,8 @@ public function register() $this->registerUrlGenerator(); $this->registerRedirector(); + + $this->registerResponseFactory(); } /** @@ -85,4 +87,17 @@ protected function registerRedirector() }); } + /** + * Register the response factory service. + * + * @return void + */ + protected function registerResponseFactory() + { + $this->app[\Illuminate\Contracts\Routing\ResponseFactory::class] = $this->app->share(function($app) + { + return new ResponseFactory($app); + }); + } + } diff --git a/src/Illuminate/Support/Facades/Response.php b/src/Illuminate/Support/Facades/Response.php index 835f7c1d9..eecd8f73d 100755 --- a/src/Illuminate/Support/Facades/Response.php +++ b/src/Illuminate/Support/Facades/Response.php @@ -1,112 +1,27 @@ make($view, $data), $status, $headers); - } - - /** - * Return a new JSON response from the application. - * - * @param string|array $data - * @param int $status - * @param array $headers - * @param int $options - * @return \Illuminate\Http\JsonResponse - */ - public static function json($data = array(), $status = 200, array $headers = array(), $options = 0) - { - if ($data instanceof ArrayableInterface) - { - $data = $data->toArray(); - } - - return new JsonResponse($data, $status, $headers, $options); - } - - /** - * Return a new JSONP response from the application. - * - * @param string $callback - * @param string|array $data - * @param int $status - * @param array $headers - * @param int $options - * @return \Illuminate\Http\JsonResponse - */ - public static function jsonp($callback, $data = [], $status = 200, array $headers = [], $options = 0) - { - return static::json($data, $status, $headers, $options)->setCallback($callback); - } +use Illuminate\Contracts\Routing\ResponseFactory as ResponseFactoryContract; + +/** + * @method static \Illuminate\Http\Response make($content = '', $status = 200, array $headers = []) + * @method static \Illuminate\Http\Response view($view, $data = [], $status = 200, array $headers = []) + * @method static \Illuminate\Http\JsonResponse json($data = [], $status = 200, array $headers = [], $options = 0) + * @method static \Illuminate\Http\JsonResponse jsonp($callback, $data = [], $status = 200, array $headers = [], $options = 0) + * @method static \Symfony\Component\HttpFoundation\StreamedResponse stream($callback, $status = 200, array $headers = []) + * @method static \Symfony\Component\HttpFoundation\BinaryFileResponse download($file, $name = null, array $headers = [], $disposition = 'attachment') + * + * @see \Illuminate\Routing\ResponseFactory + */ +class Response extends Facade { /** - * Return a new streamed response from the application. + * Get the registered name of the component. * - * @param \Closure $callback - * @param int $status - * @param array $headers - * @return \Symfony\Component\HttpFoundation\StreamedResponse + * @return string */ - public static function stream($callback, $status = 200, array $headers = array()) + protected static function getFacadeAccessor() { - return new StreamedResponse($callback, $status, $headers); - } - - /** - * Create a new file download response. - * - * @param \SplFileInfo|string $file - * @param string $name - * @param array $headers - * @param null|string $disposition - * @return \Symfony\Component\HttpFoundation\BinaryFileResponse - */ - public static function download($file, $name = null, array $headers = array(), $disposition = 'attachment') - { - $response = new BinaryFileResponse($file, 200, $headers, true, $disposition); - - if ( ! is_null($name)) - { - return $response->setContentDisposition($disposition, $name, str_replace('%', '', Str::ascii($name))); - } - - return $response; + return ResponseFactoryContract::class; } } diff --git a/src/Illuminate/Support/Traits/CapsuleManagerTrait.php b/src/Illuminate/Support/Traits/CapsuleManagerTrait.php index efb5dfa97..053275522 100644 --- a/src/Illuminate/Support/Traits/CapsuleManagerTrait.php +++ b/src/Illuminate/Support/Traits/CapsuleManagerTrait.php @@ -1,69 +1,69 @@ -container = $container ?: new Container; + /** + * The container instance. + * + * @var \Illuminate\Contracts\Container\Container + */ + protected $container; - if ( ! $this->container->bound('config')) - { - $this->container->instance('config', new Fluent); - } - } + /** + * Setup the IoC container instance. + * + * @param \Illuminate\Contracts\Container\Container $container + * @return void + */ + protected function setupContainer(Container $container) + { + $this->container = $container; - /** - * Make this capsule instance available globally. - * - * @return void - */ - public function setAsGlobal() - { - static::$instance = $this; - } + if (! $this->container->bound('config')) { + $this->container->instance('config', new Fluent); + } + } - /** - * Get the IoC container instance. - * - * @return \Illuminate\Container\Container - */ - public function getContainer() - { - return $this->container; - } + /** + * Make this capsule instance available globally. + * + * @return void + */ + public function setAsGlobal() + { + static::$instance = $this; + } - /** - * Set the IoC container instance. - * - * @param \Illuminate\Container\Container $container - * @return void - */ - public function setContainer(Container $container) - { - $this->container = $container; - } + /** + * Get the IoC container instance. + * + * @return \Illuminate\Contracts\Container\Container + */ + public function getContainer() + { + return $this->container; + } + /** + * Set the IoC container instance. + * + * @param \Illuminate\Contracts\Container\Container $container + * @return void + */ + public function setContainer(Container $container) + { + $this->container = $container; + } } diff --git a/tests/Support/SupportFacadeResponseTest.php b/tests/Support/SupportFacadeResponseTest.php index 599217d95..8365a9c1d 100755 --- a/tests/Support/SupportFacadeResponseTest.php +++ b/tests/Support/SupportFacadeResponseTest.php @@ -1,5 +1,8 @@ instance(ResponseFactoryContract::class, new ResponseFactory(null)); + + Response::clearResolvedInstances(); + Response::setFacadeApplication($app); + } + protected function tearDown(): void { + Response::setFacadeApplication(null); m::close(); } @@ -23,4 +38,26 @@ public function testArrayableSendAsJson() $this->assertEquals('{"foo":"bar"}', $response->getContent()); } + + public function testMakeWorksWithoutViewBinding() + { + $response = Response::make('hello', 201); + + $this->assertEquals('hello', $response->getContent()); + $this->assertEquals(201, $response->getStatusCode()); + } + + + public function testViewResolvesViewFactoryLazilyFromContainer() + { + $viewFactory = m::mock('StdClass'); + $viewFactory->shouldReceive('make')->once()->with('welcome', ['a' => 1])->andReturn('rendered'); + + $container = new Container; + $container->instance('view', $viewFactory); + $factory = new ResponseFactory($container); + + $this->assertEquals('rendered', $factory->view('welcome', ['a' => 1])->getContent()); + } + }