paketo-buildpacks / php

A Cloud Native Buildpack for PHP
Apache License 2.0
27 stars 9 forks source link

Buildpack support for Laravel 8 #366

Open peco8 opened 3 years ago

peco8 commented 3 years ago

Hello, I'm trying to build Llaravel 8 with php buildpacks. Here is the step.

  1. Create Laravel 8 project
composer create-project --no-cache --prefer-dist "laravel/laravel:8.0" laravel
  1. Add the following buildpack.yml
---
composer:
  vendor_directory: vendor
php:
  version: 7.4.*
  webdirectory: public
  1. Build the container image
pack build --clear-cache -b gcr.io/paketo-buildpacks/php laravel-8 --builder paketobuildpacks/builder:full
  1. Run the new image
docker run --interactive --tty --env PORT=8080 --publish 8080:8080 laravel-8

Here is the log when I acess localhsot:8080

[Sat Apr 24 04:25:07 2021] 172.17.0.1:60712 [500]: GET / - Uncaught ReflectionException: Class App\Http\Kernel does not exist in /layers/paketo-buildpacks_php-composer/php-composer-packages/vendor/laravel/framework/src/Illuminate/Container/Container.php:833
Stack trace:
#0 /layers/paketo-buildpacks_php-composer/php-composer-packages/vendor/laravel/framework/src/Illuminate/Container/Container.php(833): ReflectionClass->__construct('App\\Http\\Kernel')
#1 /layers/paketo-buildpacks_php-composer/php-composer-packages/vendor/laravel/framework/src/Illuminate/Container/Container.php(714): Illuminate\Container\Container->build('App\\Http\\Kernel')
#2 /layers/paketo-buildpacks_php-composer/php-composer-packages/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(841): Illuminate\Container\Container->resolve('App\\Http\\Kernel', Array, false)
#3 /layers/paketo-buildpacks_php-composer/php-composer-packages/vendor/laravel/framework/src/Illuminate/Container/Container.php(284): Illuminate\Foundation\Application->resolve('App\\Http\\Kernel', Array, false)
#4 /layers/ in /layers/paketo-buildpacks_php-composer/php-composer-packages/vendor/laravel/framework/src/Illuminate/Container/Container.php on line 835
[Sat Apr 24 04:25:07 2021] 172.17.0.1:60712 Closing

If my understanding is correct this problem is caused by creating a symlink of the vendor directory during the build process and ends up with autoloading failure. It would be great if you can look into this.

cnb@39425bd1d4ab:/workspace$ ll
total 352
drwxr-xr-x 12 cnb  cnb    4096 Jan  1  1980 ./
drwxr-xr-x  1 root root   4096 Apr 24 04:25 ../
-rw-r--r--  1 cnb  cnb     220 Jan  1  1980 .editorconfig
-rw-r--r--  1 cnb  cnb     829 Jan  1  1980 .env
-rw-r--r--  1 cnb  cnb     778 Jan  1  1980 .env.example
-rw-r--r--  1 cnb  cnb     111 Jan  1  1980 .gitattributes
-rw-r--r--  1 cnb  cnb     163 Jan  1  1980 .gitignore
drwxr-xr-x  2 cnb  cnb    4096 Jan  1  1980 .php.ini.d/
-rw-r--r--  1 cnb  cnb     174 Jan  1  1980 .styleci.yml
-rw-r--r--  1 cnb  cnb    3738 Jan  1  1980 README.md
drwxr-xr-x  7 cnb  cnb    4096 Jan  1  1980 app/
-rwxr-xr-x  1 cnb  cnb    1686 Jan  1  1980 artisan*
drwxr-xr-x  3 cnb  cnb    4096 Jan  1  1980 bootstrap/
-rw-r--r--  1 cnb  cnb      86 Jan  1  1980 buildpack.yml
-rw-r--r--  1 cnb  cnb    1608 Jan  1  1980 composer.json
-rw-r--r--  1 cnb  cnb  246768 Jan  1  1980 composer.lock
drwxr-xr-x  2 cnb  cnb    4096 Jan  1  1980 config/
drwxr-xr-x  5 cnb  cnb    4096 Jan  1  1980 database/
-rw-r--r--  1 cnb  cnb     974 Jan  1  1980 package.json
-rw-r--r--  1 cnb  cnb    1202 Jan  1  1980 phpunit.xml
drwxr-xr-x  2 cnb  cnb    4096 Jan  1  1980 public/
drwxr-xr-x  6 cnb  cnb    4096 Jan  1  1980 resources/
drwxr-xr-x  2 cnb  cnb    4096 Jan  1  1980 routes/
-rw-r--r--  1 cnb  cnb     563 Jan  1  1980 server.php
drwxr-xr-x  5 cnb  cnb    4096 Jan  1  1980 storage/
drwxr-xr-x  4 cnb  cnb    4096 Jan  1  1980 tests/
lrwxrwxrwx  1 cnb  cnb      67 Jan  1  1980 vendor -> /layers/paketo-buildpacks_php-composer/php-composer-packages/vendor/
-rw-r--r--  1 cnb  cnb     559 Jan  1  1980 webpack.mix.js
dmikusa commented 3 years ago

If my understanding is correct this problem is caused by creating a symlink of the vendor directory during the build process and ends up with autoloading failure.

Out of curiosity, why do you suspect the symlink is an issue? It seems to be loading other files from there OK. Where is that class expected to live?

Also, have you tried using HTTPD as the web server? The default will be PHP's built-in server. Some frameworks expect HTTPD because they include .htaccess files that have required additional configuration.

https://paketo.io/docs/buildpacks/language-family-buildpacks/php/#web-server-configuration

dmikusa commented 3 years ago

Disregard that. I was able to reproduce and get this error:

[Sat Apr 24 17:24:40 2021] PHP 7.4.16 Development Server (http://0.0.0.0:8080) started
[Sat Apr 24 17:24:46 2021] 172.17.0.1:58460 Accepted
[Sat Apr 24 17:24:46 2021] PHP Warning:  include(/layers/paketo-buildpacks_php-composer/php-composer-packages/vendor/composer/../../app/Http/Kernel.php): failed to open stream: No such file or directory in /layers/paketo-buildpacks_php-composer/php-composer-packages/vendor/composer/ClassLoader.php on line 478
[Sat Apr 24 17:24:46 2021] PHP Warning:  include(): Failed opening '/layers/paketo-buildpacks_php-composer/php-composer-packages/vendor/composer/../../app/Http/Kernel.php' for inclusion (include_path='/layers/paketo-buildpacks_php-dist/php/lib/php:/workspace/.') in /layers/paketo-buildpacks_php-composer/php-composer-packages/vendor/composer/ClassLoader.php on line 478
[Sat Apr 24 17:24:46 2021] PHP Fatal error:  Uncaught ReflectionException: Class App\Http\Kernel does not exist in /layers/paketo-buildpacks_php-composer/php-composer-packages/vendor/laravel/framework/src/Illuminate/Container/Container.php:833
Stack trace:
#0 /layers/paketo-buildpacks_php-composer/php-composer-packages/vendor/laravel/framework/src/Illuminate/Container/Container.php(833): ReflectionClass->__construct('App\\Http\\Kernel')
#1 /layers/paketo-buildpacks_php-composer/php-composer-packages/vendor/laravel/framework/src/Illuminate/Container/Container.php(714): Illuminate\Container\Container->build('App\\Http\\Kernel')
#2 /layers/paketo-buildpacks_php-composer/php-composer-packages/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(841): Illuminate\Container\Container->resolve('App\\Http\\Kernel', Array, false)
#3 /layers/paketo-buildpacks_php-composer/php-composer-packages/vendor/laravel/framework/src/Illuminate/Container/Container.php(284): Illuminate\Foundation\Application->resolve('App\\Http\\Kernel', Array, false)
#4 /layers/ in /layers/paketo-buildpacks_php-composer/php-composer-packages/vendor/laravel/framework/src/Illuminate/Container/Container.php on line 835
[Sat Apr 24 17:24:46 2021] 172.17.0.1:58460 [500]: GET / - Uncaught ReflectionException: Class App\Http\Kernel does not exist in /layers/paketo-buildpacks_php-composer/php-composer-packages/vendor/laravel/framework/src/Illuminate/Container/Container.php:833
Stack trace:
#0 /layers/paketo-buildpacks_php-composer/php-composer-packages/vendor/laravel/framework/src/Illuminate/Container/Container.php(833): ReflectionClass->__construct('App\\Http\\Kernel')
#1 /layers/paketo-buildpacks_php-composer/php-composer-packages/vendor/laravel/framework/src/Illuminate/Container/Container.php(714): Illuminate\Container\Container->build('App\\Http\\Kernel')
#2 /layers/paketo-buildpacks_php-composer/php-composer-packages/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(841): Illuminate\Container\Container->resolve('App\\Http\\Kernel', Array, false)
#3 /layers/paketo-buildpacks_php-composer/php-composer-packages/vendor/laravel/framework/src/Illuminate/Container/Container.php(284): Illuminate\Foundation\Application->resolve('App\\Http\\Kernel', Array, false)
#4 /layers/ in /layers/paketo-buildpacks_php-composer/php-composer-packages/vendor/laravel/framework/src/Illuminate/Container/Container.php on line 835

which looks like something is trying to load the file with a relative path, but that path doesn't exist there because things are relative from the symlink not from the /workspace/vendor directory. Specifically, it's trying /layers/paketo-buildpacks_php-composer/php-composer-packages/vendor/composer/../../app/Http/Kernel.php, but that should be /workspace/vendor/composer/../../app/Http/Kernel.php.

dmikusa commented 3 years ago

So there is a fragile, but pretty easy way to make this work. You can symlink back to the app directory.

If you add a file called .profile to the root of your application, and put this in it, that symlink will be created.

#!/bin/bash
ln -s /workspace/app /layers/paketo-buildpacks_php-composer/php-composer-packages/app

If you hit other similar issues, just keep adding symlinks in the same way so that PHP can find what it needs.

Unrelated note: to run your demo app, I also needed to add the openssl.so extension. It should have been auto-detected as required by the buildpack. I believe that is a bug. File here -> https://github.com/paketo-buildpacks/php-composer/issues/190.


Some notes on this...

  1. We have to make the symlink. Everything expects the vendor directory to exist in the context of the application folder, which is /workspace within the container. We cannot just install composer dependencies there though because the app files are not cached from build to build. This would mean you'd need to install everything on every run, which would be slow and a bad experience. Instead, we install to a cached layer and symlink from the app directory to the layer.
  2. I think we could copy files from the cached layer to the app directory, but that makes the resulting image larger cause you duplicate the files plus you pay an I/O penalty for copying what could be a lot of files. My thinking is that would end up being about the same as if we just reinstalled every run.
  3. I don't think we can reconfigure PHP to treat the symlink differently (although if someone knows a way, I'm listening). I think what's happening is that often people use __FILE__ or __DIR__ to require / include other code. That is supposed to give a link to the current directory, then you can use a relative location to load your other code. The behavior of these seems to resolve the symlink, which is why it's pulling in the path to the layer, not the app. However, the app's author has assumed the vendor directory will live alongside the app which is why it can't find the files, they don't exist in the layer.
  4. One hacky way that comes to mind we could make this work is basically just reverse symlink back to things in the app directory. For every top-level file or folder in /workspace, we make a symlink from the layer directory where we store the vendor directory back to the app file. This is exactly what I'm doing in the workaround script above, it's just that the buildpack would automate it and doing it in a less fragile way. I think we need to recreate these on every build too, because the contents of the /workspace can change on every build. That said, I think the overhead of creating some symlinks is less than copying a ton of files.
Birdrock commented 3 years ago

@dmikusa-pivotal Hello from cf-for-k8s. I have a user facing issues with a Laravel app built with the PHP buildpack via Kpack. When I try to follow the same steps, I get 500 codes when trying to visit the / path.

manifest.yml

---
applications:
- name: laravel
  memory: 100M
  buildpack: paketo-buildpacks/php
  env:
    APP_DEBUG: false
    CF_STAGING_TIMEOUT: 15
    CF_STARTUP_TIMEOUT: 15

buildpack.yml

composer:
  vendor_directory: vendor
php:
  # directory where web app code is stored
  # default: htdocs
  webdirectory: public

I add the .profile file you mentioned.

While Kpack might not be your area of expertise, it is still using Paketo Buildpacks to construct the application image, so I was hoping you'd have some thoughts.

dmikusa commented 3 years ago

@Birdrock what do you see in the server logs when the 500 happens? It should tell you why it's complaining.

The .profile script hack above should work, at least it did for the sample app I used. An actual app might be more complicated, possibly more symlinks to create so everything can be loaded. The server logs should tell us what's happening. You might also want to put in something like echo 'Profile script running' just so you can confirm that the script is getting picked up. You should then see that in the app logs prior to the server starting up.

Hope that helps!

Birdrock commented 3 years ago

Hi @dmikusa-pivotal,

I should have followed the link to the bug you reported. the missing OpenSSL extension seems to have been the problem. Thanks for the followup!

fg-j commented 2 years ago

@paketo-buildpacks/php-maintainers This has been open for a while. Is there a need for a better solution than the workaround @dmikusa-pivotal offered? Would this problem be easier to fix after the PHP buildpack is restructured?