laravel 导入 excel 报错排查记录
发表于|更新于|laravel
|总字数:151|阅读时长:1分钟|浏览量:
今天公司项目导入 excel 时突遇一个报错:Undefined index: Sheet1,一下给我整懵逼了,一通排查,发现是因为执行过 composer update,把一个包升级到了最高,然后,它就不能用了。
看官方 issues 说:
This is a known bug in PhpSpreadsheet PHPOffice/PhpSpreadsheet#1895. Until they release a new version, you have to lock the phpspreadsheet version to 1.16
啥意思呢,翻译一下吧:
这是PhpSpreadsheet PHPOffice / PhpSpreadsheet#1895中的一个已知错误。在他们发布新版本之前,您必须将phpspreadsheet版本锁定为1.16
版本太高有时候也不是个好事呀!
文章作者: m-finder
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 M-finder!
相关推荐

2019-03-15
Laravel 服务容器
在 Laravel 生命周期中,我们了解到框架运行过程中,会通过创建应用实例来完成很多事情,这个应用实例,也就是我们今天的主角,服务容器。 Laravel 的服务容器,是用于管理类的依赖和执行依赖注入的工具。 依赖注入 DI开始之前,需要我们先了解一下,什么是依赖注入。 简单来说,就是将类的依赖通过参数或其他方式注入。 比如: 12345678910111213141516171819202122232425262728293031323334interface Storage{ public function set($key, $value); public function get($key);}class SessionStorage implements Storage{ function __construct($cookieName='PHPSESSID'){ session_name($cookieName); session_start(); } function set($key, $value){ $_SESSION[$key] = $value; } function get($key){ return $_SESSION[$key]; }}class User{ private $storage; function __construct(Storage $storage){ $this->storage = $storage; } function setLanguage($language){ $this->storage->set('language', $language); }}$storage = new SessionStorage('SESSION_ID');$user = new User($storage); 依赖注入 并不局限于构造函数,也可以通过设值方法注入,或者类成员变量方式,通过构造函数注入适用于必要的依赖,设值注入适用于可选依赖,比如项目需要一个缓存功能的实现。在上面的例子中,我们如果需要改用 Redis 或者 MongoDB 来存储数据,只需要继承并实现 Storage 接口,然后在外部就可以很轻松的切换服务了。 依赖注入容器 IOC在实际的开发中,用上边的依赖注入方式还是很累的,所以,我们还需要了解一个新的概念,依赖注入容器,也可以叫控制反转。 简单来说,依赖注入容器就是将组件间的依赖关系由程序内部提到外部容器来管理,也就是将依赖的配置和使用分开,原本是程序控制执行流程,现在程序反倒成了被控制的对象,也就形成了控制反转。 通常用于管理大量依赖组件的实例。比如一个框架。 首先,我们可以定义一个容器: 123456789101112131415class Container{ public function getStorage() { return new SessionStorage(); } public function getUser() { return new User($this->getStorage()); }}// 更改实例化方式$container = new Container();$user = $container->getUser(); 在这个容器中,我们只需要调用容器 getUser 方法,既可以获取到 User 实例,并不需要关心它是怎么创建出来的。 但是,这个容器还存在一些问题,Storage 的实例化还是硬编码,如果要切换其他服务,只能通过改代码的方式。 对此,我们可以再次升级容器: 1234567891011121314151617181920212223242526272829303132333435363738394041class Container{ protected $binds; protected $instances; // 绑定 public function bind($abstract, $concrete) { // 判断是否为匿名函数 if ($concrete instanceof Closure) { $this->binds[$abstract] = $concrete; } else { $this->instances[$abstract] = $concrete; } } // 实例化 public function make($abstract, $parameters = []) { if (isset($this->instances[$abstract])) { return $this->instances[$abstract]; } array_unshift($parameters, $this); return call_user_func_array($this->binds[$abstract], $parameters); }}$container = new Container;$container->bind('Storage', function($container){ return new SessionStorage;});$container->bind('User',function($container,$module){ return new User($container->make($module));});$user = $container->make('User',['Storage']); 一个类似于 laravel 的服务容器就好了,当然 larave 的服务容器比这个要复杂的多。 总的来说,laravel 的服务容器有两大功能: 注册基础服务 管理需要实例化的类及其依赖 Laravel 服务容器的使用方法laravel 服务容器在使用时一般分为两个阶段:使用之前进行绑定(bind)完成将实现绑定到接口;使用时对通过接口解析(make)出服务。 laravel 内置多种不同的绑定方法以用于不同的使用场景: bind 简单绑定 singleton 单例绑定 instance 实例绑定 contextual-binding 上下文绑定 还有好几种,看文档吧 它们的最终目标是一致的:绑定接口到实现。 这样的好处是在项目的编码阶段建立起接口和实现的映射关系,到使用阶段通过抽象类(接口)解析出它的具体实现,这样就实现了项目中的解耦。 bindbind 方法的功能是将实现与接口进行绑定,然后在每次执行服务解析操作时,Laravel 容器都会重新创建实例对象。 例如: 123456789101112131415161718$this->app->bind( UserRepositoryInterface::class, UserRepository::class);class UserController extends Controller{ private $repository; function __construct(UserRepositoryInterface $userRepository) { $this->repository = $userRepository; } function users(){ return $this->repository->all(); }} 在服务提供者中,将 User 仓库的具体实现与接口进行绑定,使用时可以直接通过接口注入依赖。 singleton采用单例绑定时,仅在首次解析时创建实例,后续使用 make 进行解析服务操作都将直接获取这个已解析的对象,实现共享操作。 绑定处理类似 bind 绑定,只需将 bind 方法替换成 singleton 方法即可。 instance将已经创建的实例对象绑定到接口以供后续使用,这种使用场景类似于注册表。 比如用于存储用户模型: 12345678// 创建一个用户实例$artisan = new User();// 将实例绑定到服务容器App::instance('login-user', $artisan);// 获取用户实例$artisan = App::make('login-user'); contextual-binding主要用于一个接口多处实现,然后根据不同控制器去进行判断具体应该用哪个实现。 12345$this->app->when(PhotoController::class) ->needs(Filesystem::class) ->give(function () { return Storage::disk('local'); });

2019-04-11
laravel 测试模块学习
学习下 larave 内置的测试模块。 当你想把一些东西写到 print 语句或者调试表达式中时,别这么做,将其写成一个测试来代替。 –Martin Fowler 最开始看到的关于 laravel 测试的信息是借助模型工厂来生成测试数据,今天查完资料发现,这只是测试中的一小部分。 laravel 内置了 PHPUnit 来做测试,并且已经做好了配置文件,还提供了一些便利的辅助函数,可以更直观的测试程序。 在 laravel 的项目中,包含一个 tests 目录,这个目录又有两个子目录:Feature 和 Unit 分别用来做功能测试和单元测试。 功能测试用于测试较大区块的代码,包括若干组件之间的交互,甚至一个完整的 HTTP 请求。 单元测试用于小的 、相互隔离的代码。 配置可以使用默认的配置,也可以创建一个 .env.testing 文件,在运行测试或执行带有 --env=testing 开关的 Artisan 命令时覆盖 .env 文件中的环境变量。 创建 & 运行测试运行 artisan 生成测试用例: 12345// 在 Feature 目录下创建测试类php artisan make:test UserTest// 在 Unit 目录下创建测试类php artisan make:test UserTest --unit 先生成一个单元测试,然后改造一下: 1234$user = DB::table('users')->where('id',1)->first();$name = $user->name;$username = ucfirst($user->name);$this->assertEquals($username, $name); 把用户名首s字母大写,然后判断和原用户名是否相等。 运行结果如下: HTTP测试新建一个路由和方法: 1234567Route::get('/user-info/{id?}', 'HomeController@userInfo');public function userInfo(){ $id = request('id'); $user = User::where('id', $id)->first(); return $user;} 新建测试12345678910111213141516php artisan make:test HttpStatusTest$user = DB::table('users')->where('id',1)->first();$username = ucfirst($user->name);$response = $this->get('/user-info/1');$response->assertStatus(200)->assertJson([ 'id' => 1, 'name'=> 'wu', 'email'=> 'yf-wu@qq.com', 'email_verified_at'=>null, 'created_at'=> '2019-04-09 07:36:52', 'updated_at'=> '2019-04-09 07:36:52']); 运行测试发现接口需要登录,所以会被拦截: 所以需要先模拟用户登录,可以使用:Auth::loginUsingId(1); 使 id 为 1 的用户强制登录。 再次运行,结果 ok。 数据库测试数据库测试功能点更多,可以验证表中是否存在某条数据,也可以用来生成测试数据等。 123$this->assertDatabaseHas('users', [ 'email' => 'sally@example.com']); 也可以使用 assertDatabaseMissing 帮助程序断言数据库中不存在数据。 生成模型工厂运行命令生成模型工厂: 1php artisan make:factory PostFactory 在项目的 database\factories 目录中,已经预先生成了一个 UserFactory: 123456789$factory->define(User::class, function (Faker $faker) { return [ 'name' => $faker->name, 'email' => $faker->unique()->safeEmail, 'email_verified_at' => now(), 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 'remember_token' => Str::random(10), ];}); 改造之前的 TestUser 来测试一下: 123456public function testExample() { $user = factory(User::class)->create(['email' => 'm@m-finder.com']); $this->assertDatabaseHas('users', [ 'email' => 'm@m-finder.com' ]);} 状态ok。 更多操作还是要参考: [ PHPUnit ]

2019-08-02
在vue中使用laravel-permission的@can标签
1. 在需要权限校验的 model 中新增方法12345678910public function getAllPermissionsAttribute() { $permissions = $this->getAllPermissions(); $permission_names = []; collect($permissions)->map(function ($permission) use (&$permission_names) { $permission_names[] = $permission->name; }); return $permission_names;} 2. 在 app.blade 中存储当前用户的所有权限1234567<script> @auth window.Permissions = @json(Auth::user()->allPermissions); @else window.Permissions = []; @endauth</script> 3. 新建 vue component123456789<script> export default { methods: { $can(permissionName) { return Permissions.indexOf(permissionName) !== -1; } } };</script> 4. 在 app.js 注册 vue 组件12import auth from './components/AuthComponent';\Vue.mixin(auth); 5. 在vue中使用1<a v-if="$can('admin.admins.delete')" class="text-danger" @click="dataDelete(admin)">

2019-10-12
在 laradock 环境中使用 laravel-swoole 的 websocket
之前写了一篇 在 laradock 环境中使用 laravel-swoole 加速你的 laravel 应用 的博客,算是敲开了 laravel-swoole 的大门,今天就继续研究下期中的 websocket 功能。 安装 laravel-swoole这里就不写了,参考上篇。 配置 socket编辑 laravel env 文件,写入如下一行: 1SWOOLE_HTTP_WEBSOCKET=true 然后执行 php artisan swoole:http infos 查看状态 1234567891011121314151617+-----------------+-----------------------------------------------------+| Name | Value |+-----------------+-----------------------------------------------------+| PHP Version | 7.2.21-1+ubuntu16.04.1+deb.sury.org+1 || Swoole Version | 4.4.4 || Laravel Version | 5.8.33 || Listen IP | workspace || Listen Port | 1215 || Server Status | Online || Reactor Num | 2 || Worker Num | 2 || Task Worker Num | 2 || Websocket Mode | On || Master PID | 326 || Manager PID | 327 || Log Path | /var/www/laravel-learn/storage/logs/swoole_http.log |+-----------------+-----------------------------------------------------+ 可以看到 Websocket Mode 一项已经打开了。 编辑 socket 路由在 routes/websocket.php 中: 1234567891011121314Websocket::on('connect', function ($websocket, Request $request) { // called while socket on connect $websocket->emit('message', 'welcome');});Websocket::on('disconnect', function ($websocket) { // called while socket on disconnect $websocket->emit('message', 'server is gone, bye~');});Websocket::on('example', function ($websocket, $data) { $websocket->emit('message', $data);}); 此文件修改后需要重启 swoole:http。 前端连接 socket因为 laravel-swoole 使用的是 Socket.io,所以这里也采用这个。 12345678910111213141516<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.1.1/socket.io.js"></script><script type="text/javascript"> var socket = io('http://study.test:8080/', {transports: ['websocket'], reconnection: true}); socket.on('connect', function () { socket.emit('example', 'hi'); socket.on('disconnect', function () { console.log('disconnect'); }); socket.on('message', function (msg) { console.log(msg) }); });</script> 配置 nginx如果你用的 swoole 版本为 4.4.4,那么还需要对上篇文章中的 nginx 配置做个小修改,即添加一行: 1proxy_http_version 1.1; 完整配置就不贴了,把这一行加在 proxy_pass http://swoole-http$suffix; 这一行前后都行。 如果没有这一行的话,而且怎么也没反应的话,记得去 log 中找具体报错。 emmm,文档没写,在 issue 里扒出来的……

2019-03-14
laravel 内置 vue 的使用
从 5.3 版本开始,用 Vue.js 作为默认 JavaScript 前端框架。 从刚接触 laravel 到现在已经又过去了四个版本,种种原因,还是一直没能用上 vue.js 来做开发,现在刚好因为公司项目用到了 vue,对 vue 有了一定的了解,所以顺便就研究下 vue 在 laravel 中的使用吧。 安装laravel操作均在 laradock 的环境中进行。进入 workspace 容器,执行以下命令安装 laravel 1composer create-project laravel/laravel study 配置mysqldocker-compose up -d nginx mysql phpmyadmin 启动容器配置 nginx、hosts 并重启 nginx进入 mysql 容器执行以下命令: 123456mysql -uroot -prootALTER USER root IDENTIFIED WITH mysql_native_password BY 'PASSWORD';exit;exit 访问 phpmyadmin: localhost:8080,host 填写 mysql,用户名密码均为 root。 配置laravel修改数据库信息,生成用户模块并安装前端脚手架: 1234567891011121314php artisan make:authphp artisan migratephp artisan make:seed UsersTableSeeder在 run 方法中添加用户信息:$user = new App\User;$user->name = 'wu';$user->email = 'yf-wu@qq.com';$user->password = Hash::make('111111');$user->save();再去 DatabaseSeeder 中打开 run 中的注释,接着往下执行:php artisan db:seednpm install 修改视图home.blade.php:vue 的组件在 resources/js/components,然后在 app.js 中注册。 12You are logged in!<example-component></example-component> 更新脚手架:npm run dev or npm run watch 再实验下例子来自:[ cxp1539 ] 视图组件: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657<template> <transition name="fade"> <div v-if="isShow" class="goTop" @click="goTop"> <span class="glyphicon glyphicon-menu-up"></span> </div> </transition></template><script>export default { data() { return { isShow: false } }, mounted() { const that = this $(window).scroll(function() { if ($(this).scrollTop() > 50) { that.isShow = true } else { that.isShow = false } }) }, methods: { goTop() { $('html,body').animate({ scrollTop: 0 }) } }}</script><style scoped lang="scss"> .fade-enter-active, .fade-leave-active { transition: opacity .5s; } .fade-enter, .fade-leave-to { opacity: 0; } .goTop { position: fixed; right: 36px; bottom: 50px; background: #FFF; width: 50px; height: 50px; line-height: 60px; text-align: center; border-radius: 2px; box-shadow: 0 4px 12px 0 rgba(7,17,27,.1); cursor: pointer; z-index: 1000; span { font-size: 20px; } }</style> app.js 注册: 1Vue.component('go-top', require('./components/GoTop.vue')); 在 app.blade.php 中引入组件: 1234<main class="py-4"> @yield('content')</main><go-top></go-top> 为了使页面更高,随便修改个样式使滚动条出现。 注意事项 每次修改组件后都需要重新运行一次 npm run dev,也可以用 watch-poll 监听。 进阶使用到了上一步已经可以完成一些基础的操作了,实际上,刚才得操作还用到了一个叫做 laravel-mix 的东西,在 [ LearnKu ] (laravel-china 社区)社区的文档中是这么介绍的: Laravel Mix 提供了简洁且可读性高的 API ,用于使用几个常见的 CSS 和 JavaScript 预处理器为应用定义 Webpack 构建步骤。可以通过简单链式调用来定义资源的编译。 Laravel Mix 是叠加于 webpack 上的一层干净的膜, 它让 webpack 百分之80的用例变得十分简单。 也就是说,laravel-mix 是用来简化 webpack 学习和开发成本的工具。 对于后端人员来说,前端东西真的太多太难,mix 可以让我们不需要关注 webpack 的配置,即可轻松的编译前端脚本。 之前因为没在框架中用过 vue,所以一直也没有接触到这个工具,现在看完发现,学习之路真的是永无止境… 😂

2019-04-12
laravel 广播系统学习
看到广播系统,先想起了曾经虐过我的即时通讯。 虽然都是对 websocket 的应用,但是好像又有点区别,这里好好学习一下。 laravel 的广播与事件紧密相关,广播即对事件进行广播,因此在学习广播之前,要先阅读事件和监听器的相关文档。 配置老规矩,先来看配置文件 config/broadcasting.php 里边的配置选项: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960<?phpreturn [ /* |-------------------------------------------------------------------------- | Default Broadcaster |-------------------------------------------------------------------------- | | This option controls the default broadcaster that will be used by the | framework when an event needs to be broadcast. You may set this to | any of the connections defined in the "connections" array below. | | Supported: "pusher", "redis", "log", "null" | */ 'default' => env('BROADCAST_DRIVER', 'null'), /* |-------------------------------------------------------------------------- | Broadcast Connections |-------------------------------------------------------------------------- | | Here you may define all of the broadcast connections that will be used | to broadcast events to other systems or over websockets. Samples of | each available type of connection are provided inside this array. | */ 'connections' => [ 'pusher' => [ 'driver' => 'pusher', 'key' => env('PUSHER_APP_KEY'), 'secret' => env('PUSHER_APP_SECRET'), 'app_id' => env('PUSHER_APP_ID'), 'options' => [ 'cluster' => env('PUSHER_APP_CLUSTER'), 'encrypted' => true, ], ], 'redis' => [ 'driver' => 'redis', 'connection' => 'default', ], 'log' => [ 'driver' => 'log', ], 'null' => [ 'driver' => 'null', ], ],]; 默认情况下,laravel 提供了以上几种开箱即用的广播驱动器程序。 env 配置文件中,默认的驱动为 log,意味着客户端不会受到任何信息,只是会把要广播的消息写入 log 文件中,跟学习目标不符,就先以 pusher 展开学习吧。 我们就以发布新文章后推送给所有用户为例。 前期准备开始之前,必须要先注册 App\Providers\BroadcastServiceProvider,在 config/app.php 配置文件中的 providers 数组中取消对提供者的注释。 注册: [ pusher ] 然后把相关参数配置到 .env 文件。 安装组件:12composer require pusher/pusher-php-servernpm install --save laravel-echo pusher-js 添加文章模块,包含 migrate,controller,model,view 和 router 等内容。 新建事件:123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051php artisan make:event NewArticleNotificationEvent// 事件内容:<?phpnamespace App\Events;use Illuminate\Queue\SerializesModels;use Illuminate\Broadcasting\Channel;use Illuminate\Broadcasting\PrivateChannel;use Illuminate\Foundation\Events\Dispatchable;use Illuminate\Broadcasting\InteractsWithSockets;use Illuminate\Contracts\Broadcasting\ShouldBroadcast;use App\Article;class NewArticleNotificationEvent implements ShouldBroadcast{ use Dispatchable, InteractsWithSockets, SerializesModels; private $article; /** * Create a new event instance. * * @return void */ public function __construct(Article $article) { $this->article = $article; } public function broadcastWith() { return [ 'title' => $this->article->title, 'content' => $this->article->content, 'author' => $this->article->user->name ]; } /** * Get the channels the event should broadcast on. * * @return \Illuminate\Broadcasting\Channel|array */ public function broadcastOn() { return new Channel('articles'); }} 触发事件在保存文章的控制器中触发事件: 123$data = array_merge($request->only(['title', 'content']), ['uid' => Auth::id()]);$article = Article::create($data);broadcast(new NewArticleNotificationEvent($article)); 前端监听文章列表用了vue组件,在这个组件中进行事件监听。 1234567891011121314151617181920212223242526272829303132333435<template> <div class="container"> <table class="table table-striped"> <tr> <th>ID</th> <th>Author</th> <th>Title</th> <th>Content</th> <th>Created At</th> </tr> <tr v-for="article in articles"> <td>{{article.id}}</td> <td>{{article.user.name}}</td> <td>{{article.title}}</td> <td>{{article.content}}</td> <td>{{article.created_at}}</td> </tr> </table> </div></template><script>export default { props: ['articles'], created() { Echo.channel('articles').listen('NewArticleNotificationEvent', (article) => { console.log(article); }) }}</script><style scoped></style> 写好后要在命令行执行 npm run watch-poll 实时编译文件。 测试写篇文章测试一下: 注意事项 不需要创建 channel 路由 不需要开启队列监听 如果没反应请先强制刷新浏览器