Skip to content

Commit 1a56551

Browse files
authored
Non-route model bindings, add qs to useQuery (#9)
* wip * wip * wip * wip * wip * wip * wip * qs prop for usequery * wip * add npm publish action * use build commands * ignore claude settings * use window.location.href instead of vue-router hook
1 parent 4d642d8 commit 1a56551

File tree

28 files changed

+1817
-317
lines changed

28 files changed

+1817
-317
lines changed

.github/workflows/publish.yml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: Publish Packages
2+
3+
on:
4+
push:
5+
branches: [main]
6+
tags: ["v*"]
7+
workflow_dispatch:
8+
9+
jobs:
10+
publish:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Checkout repository
15+
uses: actions/checkout@v4
16+
17+
- name: Setup Bun
18+
uses: oven-sh/setup-bun@v2
19+
with:
20+
bun-version: latest
21+
22+
- name: Install dependencies
23+
run: bun install
24+
25+
- name: Build viper-react
26+
run: bun run react:build
27+
28+
- name: Build viper-vue
29+
run: bun run vue:build
30+
31+
- name: Setup npm authentication
32+
run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc
33+
34+
- name: Publish viper-react
35+
run: cd packages/viper-react && npm publish --access public
36+
env:
37+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
38+
39+
- name: Publish viper-vue
40+
run: cd packages/viper-vue && npm publish --access public
41+
env:
42+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
43+
44+
- name: Publish vite-plugin-viper
45+
run: cd packages/vite-plugin-viper && npm publish --access public
46+
env:
47+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,5 @@ phpunit.xml
2929
phpstan.neon
3030
testbench.yaml
3131
/coverage
32+
33+
.claude/settings.local.json

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
# Changelog
22

3-
All notable changes to `viper` will be documented in this file.
3+
## v0.3.0
4+
5+
- Added a `bind` option to queries and mutations (#9)
6+
- Added a `qs` option to queries (#9)
7+
- Sync all packages to same semver

biome.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,14 @@
2121
"rules": {
2222
"recommended": true,
2323
"suspicious": {
24-
"noExplicitAny": "warn"
24+
"noExplicitAny": "warn",
25+
"noConfusingVoidType": "warn"
26+
},
27+
"performance": {
28+
"noAccumulatingSpread": "warn"
29+
},
30+
"complexity": {
31+
"noBannedTypes": "warn"
2532
}
2633
}
2734
},

bun.lock

Lines changed: 231 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"nikic/php-parser": "^5.4",
1818
"spatie/laravel-data": "^4.15",
1919
"spatie/laravel-package-tools": "^1.16",
20+
"spatie/laravel-ray": "^1.40",
2021
"spatie/typescript-transformer": "^2.5"
2122
},
2223
"require-dev": {

docs/astro.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export default defineConfig({
4040
{ label: "Routing", link: "/guides/routing" },
4141
{ label: "Props / Queries", link: "/guides/props" },
4242
{ label: "Actions / Forms", link: "/guides/actions" },
43+
{ label: "Model / Container Injection", link: "/guides/binding" },
4344
{ label: "TypeScript", link: "/guides/typescript" },
4445
{ label: "Code Editors", link: "/guides/editors" },
4546
],

docs/src/content/docs/getting-started/installation.mdx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,22 @@ Publish the viper config:
145145
php artisan vendor:publish --tag="viper-config"
146146
```
147147

148+
Update your config to have the proper mode and framework:
149+
150+
```php
151+
// config/viper.php
152+
153+
return [
154+
// vue | react
155+
'framework' => 'vue',
156+
'output_path' => base_path('.viper'),
157+
'pages_path' => resource_path('js/pages'),
158+
// sfc -> <php> tag inside vue files
159+
// adjacent -> php files directly next to vue files ie js/pages/login.vue and js/pages/login.php
160+
'mode' => 'adjacent',
161+
];
162+
```
163+
148164
### Frontend
149165

150166
Install the required npm packages
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
---
2+
title: Model / Container Injection
3+
---
4+
import FrameworkTabsCode from '../../../components/framework-tabs-code.astro'
5+
import FrameworkTabs from '../../../components/framework-tabs.astro'
6+
import {Aside} from "@astrojs/starlight/components";
7+
8+
Props and Actions can take can inject dependencies just like regular Laravel controllers.
9+
10+
```php
11+
use Ozmos\Viper\Attrs\Prop;
12+
use Illuminate\Http\Request;
13+
14+
return new class {
15+
#[Prop]
16+
public function user(Request $request)
17+
{
18+
return $request->user();
19+
}
20+
};
21+
```
22+
23+
## Model Injection
24+
25+
Models can be injected from the current URL just like Laravel's route model binding feature, or by using our `#[Bind]` attribute.
26+
27+
### Route model binding
28+
29+
You can configure how we resolve route model bindings in your `AppServiceProvider`.
30+
31+
```php
32+
// app/Providers/AppServiceProvider.php
33+
34+
<?php
35+
36+
namespace App\Providers;
37+
38+
use Illuminate\Support\ServiceProvider;
39+
use Ozmos\Viper\Facades\Viper;
40+
41+
class AppServiceProvider extends ServiceProvider
42+
{
43+
public function boot(): void
44+
{
45+
// this is the default -> enabled and looks for models in the "\App\Models" namespace
46+
Viper::autoDiscoverModels();
47+
48+
// you can also configure a custom namespace to look in
49+
Viper::autoDiscoverModels("\\App\\V2\\Models");
50+
51+
// or disable model discovery completely
52+
Viper::autoDiscoverModels(false);
53+
}
54+
}
55+
```
56+
57+
Then when you have a route parameter in your filename like `pages/posts/[post].php` if you typehint a model that matches the PascalCased version of the filename parameter we will inject it into your method.
58+
59+
```php
60+
// resources/js/pages/posts/[post].php
61+
return new class {
62+
#[\Ozmos\Viper\Attrs\Prop]
63+
public function post(\App\Models\Post $post)
64+
{
65+
return $post;
66+
}
67+
};
68+
```
69+
70+
### The `Bind` attribute
71+
72+
<Aside type={"tip"}>
73+
This works for both Props and Actions
74+
</Aside>
75+
76+
Sometimes you need to inject a model that isn't reflected in the url.
77+
78+
Say you have an `/admin/users` route that lists all the users and you want to show the details in a modal when one of them is clicked on instead of redirecting to a page like `/admin/users/{user}`. In this case we can tell Viper that we want a model resolved manually by using the `#[Bind]` attribute.
79+
80+
81+
```php
82+
use Ozmos\Viper\Attrs\Prop;
83+
use Ozmos\Viper\Attrs\Bind;
84+
use Illuminate\Http\Request;
85+
86+
return new class {
87+
#[Prop]
88+
public function user(#[Bind] User $user)
89+
{
90+
return $user;
91+
}
92+
};
93+
```
94+
95+
Then in our frontend code we just need to pass the `bind` prop to our query. The key must be the same name as the php method parameter (typescript will also give you an error if you do not pass it correctly).
96+
97+
<FrameworkTabs>
98+
<Fragment slot="vue">
99+
```vue
100+
<script setup lang={'ts'}>
101+
import { usePage } from '@ozmos/viper-vue';
102+
103+
const page = usePage<ViperGen.AdminUsers>();
104+
105+
const selectedUserId = ref(1);
106+
107+
const { data: user } = page.useQuery('user', {
108+
bind: {
109+
user: selectedUserId,
110+
},
111+
});
112+
</script>
113+
```
114+
</Fragment>
115+
<Fragment slot={'react'}>
116+
```tsx
117+
import { usePage } from '@ozmos/viper-react';
118+
import { useState } from 'react';
119+
120+
export default function Page() {
121+
const page = usePage<ViperGen.AdminUsers>();
122+
const [selectedUserId, setSelectedUserId] = useState(1);
123+
124+
const { data: user } = page.useQuery('user', {
125+
bind: {
126+
user: selectedUserId,
127+
},
128+
});
129+
}
130+
```
131+
</Fragment>
132+
</FrameworkTabs>
133+
134+
You can bind multiple models by adding multiple parameters to your function with bind attributes. Ensure that the method parameter name matches that of the frontend bind object keys.
135+
136+
You can customise the column that is used to resolve the model. By default this is going to check the `id` attribute on the model but you can specify any column in the attribute:
137+
138+
```php
139+
use Ozmos\Viper\Attrs\Prop;
140+
use Ozmos\Viper\Attrs\Bind;
141+
use Illuminate\Http\Request;
142+
143+
return new class {
144+
#[Prop]
145+
public function post(#[Bind(column: 'slug')] Post $post)
146+
{
147+
return $post;
148+
}
149+
};
150+
```

docs/src/content/docs/guides/props.mdx

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,65 @@ return new class {
9292
`}
9393
/>
9494

95+
## Query String Parameters
96+
97+
Query strings are automatically available from the current url in the browser but you can pass additional query string parameters using the `qs` option if needed. Properties passed to `qs` won't be added to the browsers URL, only the underlying query.
98+
99+
Note that the values passed via `qs` won't be available in your php function on first page load, so this is only really recommended to be used for lazy props.
100+
101+
<FrameworkTabsCode
102+
filename="example"
103+
vue={`
104+
<script setup lang="ts">
105+
import { usePage } from '@ozmos/viper-vue';
106+
import { ref } from 'vue';
107+
108+
const page = usePage<ViperGen.Example>();
109+
const search = ref('');
110+
111+
const { data: results } = page.useQuery('searchResults', {
112+
qs: {
113+
q: search,
114+
limit: 10,
115+
tags: ['active', 'featured']
116+
}
117+
});
118+
</script>
119+
`}
120+
react={`
121+
import { usePage } from '@ozmos/viper-react';
122+
import { useState } from 'react';
123+
124+
export default function Example() {
125+
const page = usePage<ViperGen.Example>();
126+
const [search, setSearch] = useState('');
127+
128+
const { data: results } = page.useQuery('searchResults', {
129+
qs: {
130+
q: search,
131+
limit: 10,
132+
tags: ['active', 'featured']
133+
}
134+
});
135+
136+
return null;
137+
}
138+
`}
139+
php={`
140+
return new class {
141+
#[\\Ozmos\\Viper\\Attrs\\Prop]
142+
public function searchResults(\\Illuminate\\Http\\Request $request): array
143+
{
144+
$query = $request->get('q');
145+
$limit = $request->get('limit', 20);
146+
$tags = $request->get('tags', []);
147+
148+
return Post::search($query)->limit($limit)->get();
149+
}
150+
};
151+
`}
152+
/>
153+
95154
## Lazy Loading
96155

97156
If you don't want to fetch the prop on the first visit of the page you can pass `lazy: true` to the attribute.

0 commit comments

Comments
 (0)