在 Laravel 6 中,运行包含 PHP Blade 语法的文件,进而获取其输出

1、现有一段 PHP Blade 代码块,放在 MySQL 中,其代码存放在字段 schema 中,如图1

图1

2、一些变量,已经在 PHP 脚本中声明,代码块如下所示

{
  "sections": {
    @foreach ($homepage as $id => $section)
      {{-- 轮播图 --}}
      @if($section['type'] === 'carousel')
        "{{ $id }}": {
          "type": "carousel",
          "settings": {
            "loop": true,
            "autoplay": true,
            "height": "{{ isset($section['slide_height']) && $section['slide_height'] === 'full' ? 'full' : 'auto' }}",
            "interval": {{ $section['interval'] }}
          },
          "blocks": {
            @if (isset($section['items']))
              @foreach ($section['items'] as $key => $item)
                "slide-{{ $key }}": {
                  "type": "slide",
                  "settings": {
                    "image": "{{ @$item['image']['url'] ?: '' }}",
                    "url": "{{ $item['button']['url'] }}",
                    "align": "{{ $item['align'] }}",
                    "title": {-- $item['title'] --},
                    "description": {-- $item['description'] --},
                    "button": {-- $item['button']['text'] --},
                    "opacity": {{ $item['show_overlay'] ? (100 - $item['opacity']) : 100 }},
                    "button_background_color": "{{ $item['button_background_color'] }}",
                    "button_color": "{{ $item['button_text_color']}}",
                    "color": "{{ $item['text_color'] }}"
                  }
                }@if (!$loop->last),@endif
              @endforeach
            @endif
          }
        },
      @endif
      {{-- 商品分类 --}}
      @if($section['type'] === 'collection')
        "{{ $id }}": {
          "type": "main-collections",
          "settings": {
            "heading": {-- $section['title'] --}
          },
          "blocks": {
            @foreach ($section['ids'] as $key => $item)
              "collection-{{ $key }}": {
                "type": "collection",
                "settings": {
                  "id": {{ $item }}
                }
              }@if (!$loop->last),@endif
            @endforeach
          }
        },
      @endif
      {{-- 精选商品 --}}
      @if($section['type'] === 'product')
        "{{ $id }}": {
          "type": "main-products",
          "settings": {
            "show_product_palette": {{ @Module::find('ProductPalette')->isEnabled() && @config('product-palette.show_position') === 'all' ? 'true' : 'false' }},
            "heading": {-- $section['title'] --},
            "layout": "{{ $section['layout'] }}",
            "page_size": {{ $section['amount'] }}
          },
          "blocks": {
            @foreach ($section['category'] as $key => $item)
              "collection-{{ $key }}": {
                "type": "collection",
                "settings": {
                  "id": {{ $item }}
                }
              }@if (!$loop->last),@endif
            @endforeach
          }
        },
      @endif
      {{-- 邮件订阅 --}}
      @if($section['type'] === 'subscribe')
        "{{ $id }}": {
          "type": "apps",
          "blocks": {
            "newsletter": {
              "type": "object/Newsletter/blocks/newsletter",
              "settings": {
                "heading": {-- $section['title'] --},
                "description": {-- $section['description'] --}
              }
            }
          }
        },
      @endif
      {{-- 图片 --}}
      @if($section['type'] === 'image')
        "{{ $id }}": {
          "type": "image",
          "settings": {
            "heading": {-- $section['title'] --},
            "heading_align": "{{ $section['align'] }}"
          },
          "blocks": {
            @if (isset($section['items']))
              @foreach ($section['items'] as $key => $item)
                "column-{{ $key }}": {
                  "type": "column",
                  "settings": {
                    "image": "{{ @$item['image']['url'] ?: '' }}",
                    "mobile_image": "{{ @$item['mb_image']['url'] ?: '' }}",
                    "url": "{{ $item['url'] }}",
                    "title": "{{ @$item['url_object']['title'] ?: '' }}"
                  }
                }@if (!$loop->last),@endif
              @endforeach
            @endif
          }
        },
      @endif
      {{-- 图文 --}}
      @if($section['type'] === 'imagetext')
        {{-- 网格 --}}
        @if($section['layout_type'] === 'grid')
          "{{ $id }}": {
            "type": "multi-column",
            "settings": {
              "heading": {-- $section['title_text'] ?: '' --},
              "heading_align": "{{ $section['title_aligns'] }}",
              "text_color": "{{ $section['text_color_v2'] }}",
              "image_size": "{{ $section['image_style'] === 'auto' ? 'auto' : 'fixed' }}",
              "mask_opacity": {{ $section['opacity'] }},
              "button_text_color": "{{ $section['button_text_color_v2'] }}",
              "button_background_color": "{{ @$section['button_bg_color_v2'] ?: '' }}",
              "text_align": "{{ $section['align'] }}"
            },
            "blocks": {
              @if (isset($section['items']))
                @foreach ($section['items'] as $key => $item)
                  "column-{{ $key }}": {
                    "type": "column",
                    "settings": {
                      "heading": {-- $item['title'] ?: '' --},
                      "text": {-- @$item['description'] ?:'' --},
                      "image": "{{ @$item['image']['url'] ?: '' }}",
                      "mobile_image": "{{ @$item['mobile_image']['url'] ?: '' }}",
                      "url": "{{ @$item['button']['url_object']['url'] }}",
                      "label": {-- $item['button']['text'] --}
                    }
                  }@if (!$loop->last),@endif
                @endforeach
              @endif
            }
          },
        @else
          {{-- 左文右图(left) --}}
          {{-- 左图右文(right) --}}
          "{{ $id }}": {
            "type": "image-text",
            "settings": {
              "heading": {-- $section['title_text'] --},
              "heading_align": "{{ $section['title_aligns'] }}",
              "image": "{{ @$section['horizontal']['image']['url'] }}",
              "image_placement": "{{ $section['layout_type'] === 'left' ? 'second' : 'first' }}",
              "horizontal_align": "{{ $section['horizontal']['level_align'] }}",
              "vertical_align": "{{ $section['horizontal']['vertical_align'] === 'flex-start' ? 'top' : ($section['horizontal']['vertical_align'] === 'flex-end' ? 'bottom' : 'middle') }}",
              "text_color": "{{ $section['text_color_v2'] }}",
              "background_color": "{{ @$section['text_bg_color_v2'] ?: '' }}"
            },
            "blocks": {
              "heading": {
                "type": "heading",
                "settings": {
                  "heading": {-- $section['horizontal']['title'] --}
                }
              },
              "text": {
                "type": "text",
                "settings": {
                  "text": {-- $section['horizontal']['description'] --}
                }
              },
              "button": {
                "type": "button",
                "settings": {
                  "url": "{{ @$section['horizontal']['button']['url_object']['url'] }}",
                  "label": {-- $section['horizontal']['button']['text'] --},
                  "text_color": "{{ $section['button_text_color_v2'] }}",
                  "background_color": "{{ $section['button_bg_color_v2'] }}"
                }
              }
            }
          },
        @endif
      @endif
    @endforeach
    "0": {
      "type": "empty"
    }
  }
}

3、编译后的模板文件代码如下所示。


{
  "sections": {
    <?php $__currentLoopData = $homepage; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $id => $section): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
      
      <?php if($section['type'] === 'carousel'): ?>
        "<?php echo e($id); ?>": {
          "type": "carousel",
          "settings": {
            "loop": true,
            "autoplay": true,
            "height": "<?php echo e(isset($section['slide_height']) && $section['slide_height'] === 'full' ? 'full' : 'auto'); ?>",
            "interval": <?php echo e($section['interval']); ?>

          },
          "blocks": {
            <?php if(isset($section['items'])): ?>
              <?php $__currentLoopData = $section['items']; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $key => $item): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
                "slide-<?php echo e($key); ?>": {
                  "type": "slide",
                  "settings": {
                    "image": "<?php echo e(@$item['image']['url'] ?: ''); ?>",
                    "url": "<?php echo e($item['button']['url']); ?>",
                    "align": "<?php echo e($item['align']); ?>",
                    "title": <?php echo json_encode($item['title'], JSON_UNESCAPED_UNICODE); ?>,
                    "description": <?php echo json_encode($item['description'], JSON_UNESCAPED_UNICODE); ?>,
                    "button": <?php echo json_encode($item['button']['text'], JSON_UNESCAPED_UNICODE); ?>,
                    "opacity": <?php echo e($item['show_overlay'] ? (100 - $item['opacity']) : 100); ?>,
                    "button_background_color": "<?php echo e($item['button_background_color']); ?>",
                    "button_color": "<?php echo e($item['button_text_color']); ?>",
                    "color": "<?php echo e($item['text_color']); ?>"
                  }
                }<?php if(!$loop->last): ?>,<?php endif; ?>
              <?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
            <?php endif; ?>
          }
        },
      <?php endif; ?>
      
      <?php if($section['type'] === 'collection'): ?>
        "<?php echo e($id); ?>": {
          "type": "main-collections",
          "settings": {
            "heading": <?php echo json_encode($section['title'], JSON_UNESCAPED_UNICODE); ?>

          },
          "blocks": {
            <?php $__currentLoopData = $section['ids']; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $key => $item): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
              "collection-<?php echo e($key); ?>": {
                "type": "collection",
                "settings": {
                  "id": <?php echo e($item); ?>

                }
              }<?php if(!$loop->last): ?>,<?php endif; ?>
            <?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
          }
        },
      <?php endif; ?>
      
      <?php if($section['type'] === 'product'): ?>
        "<?php echo e($id); ?>": {
          "type": "main-products",
          "settings": {
            "show_product_palette": <?php echo e(@Module::find('ProductPalette')->isEnabled() && @config('product-palette.show_position') === 'all' ? 'true' : 'false'); ?>,
            "heading": <?php echo json_encode($section['title'], JSON_UNESCAPED_UNICODE); ?>,
            "layout": "<?php echo e($section['layout']); ?>",
            "page_size": <?php echo e($section['amount']); ?>

          },
          "blocks": {
            <?php $__currentLoopData = $section['category']; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $key => $item): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
              "collection-<?php echo e($key); ?>": {
                "type": "collection",
                "settings": {
                  "id": <?php echo e($item); ?>

                }
              }<?php if(!$loop->last): ?>,<?php endif; ?>
            <?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
          }
        },
      <?php endif; ?>
      
      <?php if($section['type'] === 'subscribe'): ?>
        "<?php echo e($id); ?>": {
          "type": "apps",
          "blocks": {
            "newsletter": {
              "type": "object/Newsletter/blocks/newsletter",
              "settings": {
                "heading": <?php echo json_encode($section['title'], JSON_UNESCAPED_UNICODE); ?>,
                "description": <?php echo json_encode($section['description'], JSON_UNESCAPED_UNICODE); ?>

              }
            }
          }
        },
      <?php endif; ?>
      
      <?php if($section['type'] === 'image'): ?>
        "<?php echo e($id); ?>": {
          "type": "image",
          "settings": {
            "heading": <?php echo json_encode($section['title'], JSON_UNESCAPED_UNICODE); ?>,
            "heading_align": "<?php echo e($section['align']); ?>"
          },
          "blocks": {
            <?php if(isset($section['items'])): ?>
              <?php $__currentLoopData = $section['items']; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $key => $item): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
                "column-<?php echo e($key); ?>": {
                  "type": "column",
                  "settings": {
                    "image": "<?php echo e(@$item['image']['url'] ?: ''); ?>",
                    "mobile_image": "<?php echo e(@$item['mb_image']['url'] ?: ''); ?>",
                    "url": "<?php echo e($item['url']); ?>",
                    "title": "<?php echo e(@$item['url_object']['title'] ?: ''); ?>"
                  }
                }<?php if(!$loop->last): ?>,<?php endif; ?>
              <?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
            <?php endif; ?>
          }
        },
      <?php endif; ?>
      
      <?php if($section['type'] === 'imagetext'): ?>
        
        <?php if($section['layout_type'] === 'grid'): ?>
          "<?php echo e($id); ?>": {
            "type": "multi-column",
            "settings": {
              "heading": <?php echo json_encode($section['title_text'] ?: '', JSON_UNESCAPED_UNICODE); ?>,
              "heading_align": "<?php echo e($section['title_aligns']); ?>",
              "text_color": "<?php echo e($section['text_color_v2']); ?>",
              "image_size": "<?php echo e($section['image_style'] === 'auto' ? 'auto' : 'fixed'); ?>",
              "mask_opacity": <?php echo e($section['opacity']); ?>,
              "button_text_color": "<?php echo e($section['button_text_color_v2']); ?>",
              "button_background_color": "<?php echo e(@$section['button_bg_color_v2'] ?: ''); ?>",
              "text_align": "<?php echo e($section['align']); ?>"
            },
            "blocks": {
              <?php if(isset($section['items'])): ?>
                <?php $__currentLoopData = $section['items']; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $key => $item): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
                  "column-<?php echo e($key); ?>": {
                    "type": "column",
                    "settings": {
                      "heading": <?php echo json_encode($item['title'] ?: '', JSON_UNESCAPED_UNICODE); ?>,
                      "text": <?php echo json_encode(@$item['description'] ?:'', JSON_UNESCAPED_UNICODE); ?>,
                      "image": "<?php echo e(@$item['image']['url'] ?: ''); ?>",
                      "mobile_image": "<?php echo e(@$item['mobile_image']['url'] ?: ''); ?>",
                      "url": "<?php echo e(@$item['button']['url_object']['url']); ?>",
                      "label": <?php echo json_encode($item['button']['text'], JSON_UNESCAPED_UNICODE); ?>

                    }
                  }<?php if(!$loop->last): ?>,<?php endif; ?>
                <?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
              <?php endif; ?>
            }
          },
        <?php else: ?>
          
          
          "<?php echo e($id); ?>": {
            "type": "image-text",
            "settings": {
              "heading": <?php echo json_encode($section['title_text'], JSON_UNESCAPED_UNICODE); ?>,
              "heading_align": "<?php echo e($section['title_aligns']); ?>",
              "image": "<?php echo e(@$section['horizontal']['image']['url']); ?>",
              "image_placement": "<?php echo e($section['layout_type'] === 'left' ? 'second' : 'first'); ?>",
              "horizontal_align": "<?php echo e($section['horizontal']['level_align']); ?>",
              "vertical_align": "<?php echo e($section['horizontal']['vertical_align'] === 'flex-start' ? 'top' : ($section['horizontal']['vertical_align'] === 'flex-end' ? 'bottom' : 'middle')); ?>",
              "text_color": "<?php echo e($section['text_color_v2']); ?>",
              "background_color": "<?php echo e(@$section['text_bg_color_v2'] ?: ''); ?>"
            },
            "blocks": {
              "heading": {
                "type": "heading",
                "settings": {
                  "heading": <?php echo json_encode($section['horizontal']['title'], JSON_UNESCAPED_UNICODE); ?>

                }
              },
              "text": {
                "type": "text",
                "settings": {
                  "text": <?php echo json_encode($section['horizontal']['description'], JSON_UNESCAPED_UNICODE); ?>

                }
              },
              "button": {
                "type": "button",
                "settings": {
                  "url": "<?php echo e(@$section['horizontal']['button']['url_object']['url']); ?>",
                  "label": <?php echo json_encode($section['horizontal']['button']['text'], JSON_UNESCAPED_UNICODE); ?>,
                  "text_color": "<?php echo e($section['button_text_color_v2']); ?>",
                  "background_color": "<?php echo e($section['button_bg_color_v2']); ?>"
                }
              }
            }
          },
        <?php endif; ?>
      <?php endif; ?>
    <?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
    "0": {
      "type": "empty"
    }
  }
}

4、决定跳过将 PHP Blade 文件编译为 PHP 模板文件的这一步,而是手动调整为编译后的格式,然后去除掉其中的 Blade 语法。以下为编译后的一段 Blade 格式示例。如图2

图2

5、以下为去除掉其中的 Blade 语法后的示例。涉及到像 $__env 的变量已经全部删除。如图3

图3

6、决定先尝试编写一个简单的代码块,以验证技术方案的可行性。如果可行,再尝试执行步骤 5 所生成的代码块。

{
 <?php $items = [1, 2, 3]; ?>
 <?php if(isset($items)): ?>
 <?php $count = count($items); foreach($items as $key => $item):  $count--; ?>
  "slide-<?php echo $key; ?>": {
    "type": "slide",
    "settings": {
   "image": "<?php echo $item ?: ''; ?>"
    }
  }<?php if($count != 0): ?>,<?php endif; ?>
 <?php endforeach;  ?>
 <?php endif; ?>
}

7、直接运行此代码所在的文件(phpcode.php),其结果转换为 json 结构,符合预期。如图4

图4


{
     "slide-0": {
    "type": "slide",
    "settings": {
   "image": "1"
    }
  },   "slide-1": {
    "type": "slide",
    "settings": {
   "image": "2"
    }
  },   "slide-2": {
    "type": "slide",
    "settings": {
   "image": "3"
    }
  }  }


8、现在需要新建一个 PHP 脚本,然后使用输出缓冲来将 PHP 文件包含入一个字符串。参考:https://www.php.net/manual/zh/function.include.php ,新建 PHP 文件 ob.php,运行后,输出 json 结构,符合预期。如图5

图5

<?php

$string = get_include_contents('phpcode.php');
echo $string;

function get_include_contents($filename) {
    if (is_file($filename)) {
        ob_start();
        include $filename;
        $contents = ob_get_contents();
        ob_end_clean();
        return $contents;
    }
    return false;
}

?>

9、不过由于前端同事认为这样调整模板为 PHP 原生语法的方案可读性差,工作量过大。仍然要求后端自动完成所有的流程,也包含 Blade 编译为 PHP 模板的流程。最终实现方案如下

/*
$filename = $directory . '/' . substr($themeAsset->asset_key, 6);
if (Storage::disk('local')->exists($filename)) {
 ob_start();
 include storage_path('app') . '/' . $filename;
 $contents = ob_get_contents();
 ob_end_clean();
 $themeAsset->schema = $contents;
 $themeAsset->save();
}
*/View::addLocation(storage_path('app') . '/' . $directory);
$filename = substr(substr($originalThemeAsset->asset_key, 0, strpos($originalThemeAsset->asset_key, '.')), 6);
$variables = $this->getBladeSchemaCompilerVariables($filename, $originalThemeInstallation->theme);
$schema = View::make($filename)->with($variables)->render();
$themeAsset->schema = $schema;
$themeAsset->save();
永夜