Skip to main content

Webinars

Developing with Bandwidth: PHP 101

Come hangout with the Bandwidth Developer Experience team as we dive into PHP and demo what’s new with our Voice & Messaging SDKs and APIs. We’ll also share what’s new with our Developer Experience, plus give you a sneak peek at our roadmap for future PHP SDK support.

What you’ll learn

  • Install and configure your development environment in PHP
  • Setup your Bandwidth Account to start using the new APIs
  • Send and receive group MMS
  • Manage inbound and outbound phone calls

Webinar Transcript:

TRACY: Hey, good afternoon. Good morning, everybody. Thank you for joining us today. My name is Tracy, and I’m going to be your webinar host. So welcome to our Developing with Bandwidth mini series. This is the last one of the series, so kudos to Dan Tolbert for putting up with me for the last six weeks.

So before we get started, we’re going to go over a few housekeeping items. All attendees are going to be muted to prevent any background noise. I know there’s a lot of us have been working from home in the last month. A lot of those noises are probably coming in a little stronger than they were before.

So everyone will be muted. We will have time for Q&A at the end of the webinar. You should see the Q&A box on the bottom of your control panel, so feel free to type in any questions during the webinar. We’ll keep it a little informal if we see some come in. We’ll just go ahead and ask them while Dan is running through the demo.

And I just wanted to know, as I mentioned, this is the last week of our mini series. So the last several rounds we’ve covered Node.js Java, C#, Ruby, and Python. If you guys are interested in seeing any of those, head over to bandwidth.com/resources, and you can watch any of those on demand and feel free to sign up for any of our upcoming webinars there as well.

But for today, we’re going to cover PHP with Bandwidth technical product owner Dan Tolbert. Dan does the API things with Bandwidth, and he has for quite some time. And during his time with us, he’s actually works really closely with customers of all sizes during their integrations to Bandwidth.

He currently sits on the technology team, helping drive Bandwidth developer experience team forward, and he is a great demo-giver, professional demoer, if you will. So with that, I will go ahead and turn it over to Dan to get us started.

DAN TOLBERT: Awesome. Thanks, Tracy, and thanks, everyone, for hanging out with us today. So I just want to go through and touch base on where we’re going to be looking at. First thing that we want to just make sure everyone has access to the documentation. It’s going to be at dev.bandwidth.com, and we’ll be referring to this a little bit as we move forward, and I’m going to resize this window and expose a little bit more going on over here.

And then, let’s go ahead and just dive into our example that we’re going to be building today. I’ve got some scaffold set up here. Actually, delete. One little thing, spoiler alert, just a scaffold, nice, little, Slim PHP app using this Slim HTTP framework. I’ve got this running in IntelliJ, and I’ve got this in debug mode, so we can play around with what’s going on here.

And let’s talk about the app we’re going to build today. So I’m going to head to the Bandwidth examples, GitHub repo, linked here from our documentation. And I’m going to head to the PHP section and specifically the webinar example, and this is what we’re going be building today.

So you’ve got the full app is going to be tucked away under public and index, and we’ve got our initial starting point, the same as is over here in this initial index.php as I said before, this is using the Slim HTTP framework, as well as Bandwidth SDK. And the primary purpose of our webinar today is to really touch on how to use the Bandwidth SDK to send some text messages and to respond to in-bound phone calls.

So let’s talk about the app that we’re going to be building. We’re going to be allocating a phone number to our Bandwidth account. And then, we’re going to set that up so that if you text that phone number, the word “dog,” D-O-G, you’re going to get back a picture message with an emoji of a dog. And if you text anything else other than dog, we’re going to receive a response. It says hello from Bandwidth.

If you call that phone number, you’re going to be asked to play a little arithmetic game. And then, using the ability of Bandwidth to collect digits, so gather. Press 1 for tacos, 2 for pizza. We’re going to ask you a quick arithmetic question. If you get it right, we’re going to play a success sound. And if you get it wrong, we’re going to play a sad trombone sound and hang up the call, both instances.

OK. So with that, let me talk a little bit about my development environment here. I am running in IntelliJ. I’m running PHP 7.4.4 with the version 2.0 of the Bandwidth SDK, and I am using the Slim HTTP framework version 4.0. And I first am using an application called ngrok. You can see I’ve got it running over here. This allows me to expose my localhost where I’m running the server to the public internet so that I can send, or I can receive callbacks from the Bandwidth API as I interact with the system in the telephone network.

So let’s go through some of the purex when it comes to environment variables. We need our account ID. We need our API user, our API password. We’re going to need a messaging token and a messaging secret. We’re also going to need a messaging application ID, as well as a voice application ID.

And then we’re going to need to set up our application callbacks to point to this ngrok URL or your server’s URL to the messaging callback and voice callback paths that are defined here, and I have right now. And that’s going to accept a POST request, and then we’re going to do some stuff with that response or the request and generate responses from there.

OK. So let’s go to the Bandwidth dashboard. That will be dashboard.bandwidth.com. I’m already logged in, but let me show you where everything is and where to grab all your API credentials and a little bit about your API user and user password.

So first thing I want to do is head to the Account tab right here. You start to see a few things. Specifically, your account ID is going to be this right here. It’s also going to be in the URL as you browse around so that mine is 9900778, and I also have users.

So I’ve got my main user that I’m logged in with here. I also have created an API user, so you can get an idea where we have this dashboard access method, GUI being the ability to log in to the dashboard and click around like I’m doing now. The API’s restricted to API-only access, so that’s what I would be using here. I like to set an API user more like a service account whenever I’m working with the Bandwidth dashboard as it does not allow me or doesn’t require me to reset my password every 90 days.

So if you were to choose the GUI or both access methods on your API user, we will be resetting that password every 90 days, and you have to set a new one. Not always ideal if you have to go through an update, lots of servers to respond to that password update. So in here is where you would create, or you can have your user Bandwidth account manager create UA user. But this is where you would create a new API user in this account users page here, and we’ll be using that to access the voice API, as well as the phone numbers API.

Messaging is a bit different, and it uses the Bandwidth Token and Secret paradigm. So if you head to the applications page here, you have your application types on this new application. If you look at this little blob right here, and I switch from voice to messaging, the URIs change. So I want to be sure that I’m on the messaging application type.

And then I’m going to select my API credentials, and you’ll start to see this token in secret pair. I’ve already generated a token secret pair for myself and have it saved my environment variables. But I’ll just go through that again, and I click this Create new. You’ll get two token or a token and a secret pair.

Again, I copy-paste button– or not paste– but copy buttons for you there. One thing that you’ll note is that all of my previous secrets are no longer visible. They are only visible to you once, and that’s on the screen. So once you close the screen, it’s gone. No one can recover that for you. So Bandwidth, we can’t even see what they are after at this point when they’re generated, so be sure to copy and save these off to the side somewhere and take a note as to what token is related to what user.

You can perhaps have multiple tokens and secrets prepares for multiple developers developing against Bandwidth at your companies. So I’m going to delete this now since it’s been exposed, and I don’t want anyone to hack me or crack them into my account and close that. Let’s take a little bit look at our applications.

I already have two created for myself, but I can show you how to build this out. Specifically for messaging, I would grab my ngrok URL, or this is your publicly addressable URL. I need to set my paths to call back messaging and get rid of the double slash. And if I read correctly, that’s actually my callback URL, and the application name will be PHP messaging.

And then, when I create that application it’s going to generate an application ID for me. I need to copy that and be sure to save that as well. You’re going to need this every single time we create a text message for you to just head to the documentation. I can show you an idea of what that would look like. We’ll just go to HTTP, so we could see the raw request.

So every time we send a text message, we’re going to need that application ID. So we’re going to save that to an environment variable to load in. Additionally, we’re going to need a voice application ID not necessarily for this demo today but anytime you’re going to want to create a phone call and not always respond to an inbound phone call. You’re going to need a voice application. So we can go through and show a little bit of how that would work.

PHP Voice, I don’t know. Spell. It’s going to be a voice application, and I’ll just go ahead and paste in callbacks voice and slash and bound. There we go. And that’s going to be a POST or GET. I always set mine to POST when I’m building a server. It’s just easier for me to access those instead of dealing with the query parameters.

However, you can set this to be a GET request, and then we also have this call status callback URL. And this is where we’re going to send disconnect events or anything that I call non-actionable events. So any time there’s a request that we would send to you that we don’t have the ability to continue that call, so like a hang up event or something like that. We’re going to send that to the call status callback URL.

So these are non-actionable events, and the call initiated callback URL is we’re going to send the request for next steps after an inbound phone call happens. And you can see the callbacks under the voice here, call initiate. This is going to be a brand new inbound call to that Bandwidth phone number.

And finally, I’ll find out where I am. You need to associate these to an application or to a location. So a location is going to be a logical grouping of phone numbers. It’s got a bunch of different settings onto it that all the phone numbers within that location. To get to your locations, you’re going to head to your account locations page, and I’m going to add a new one.

I’m going to add it under my subaccount. I’m just going to call this PHP Demo. I do not want it to be my default location. I want to make sure that it uses HTTP voice, and I want to use my PHP voice demo application. Then I’m going to unfurl this and go to SMS settings. Enable this.

This talks to my ability to text to and from toll-free numbers or short codes. For this demo, we’re not really going to worry about that. But if you are sending from a toll-free or sending with shortcuts, you will need to be sure to enable these explicitly for every location that you have.

Protocol HTTP, I’m going to be using B2 messaging, and I need the messaging demo application. But I’m for all this to look at MMS settings. You’ll see that once I enable that, it is required to be the same as my SMS settings. Make sense, right? We only have one URL for both MMS and SMS. So we’re going to keep that to be the same.

I’m going to go ahead and create that location now, and let’s order a phone number to it just to tie this up in a bow. I’m going to head to my numbers page here. I just want to search for 12919, nothing special, and I’ll grab the first one here. Click Continue. Obviously, this is going to go to my subaccount. I’m going to assign it to my PHP demo location and then click purchase.

Yep. And at this point, if I were to text this phone number or to call this phone number, I think we can call it without breaking anything. We will see a callback be sent to my ngrok URL. So I’m going to pop over to Dialpad and make a call. Type the phone number. Call that. I think– yup. There we are.

And you can see there is my voice inbound call, and we can look at ngrok’s in that web interface. To see there are, there’s the time of day right there, and there’s my Dialpad number and the phone number that I just called. So you can see we’ve got it wired up so that we’re sending text messages or sending inbound calls at least to our ngrok URL, which is proxing to my server that is running over here. OK. That’s a lot that’s happened. Let’s just take a quick second to pause and see if there’s any questions while I grab a drink of water.

TRACY: No questions just yet. But as I said before, we can be very informal. So if you guys feel the need to ask anything, don’t hesitate.

DAN TOLBERT: OK. Water, good. Let’s bounce back over to our example. So at this point, I’ve got all of my dependencies installed. I’ve got the tutorial what you need to do there. So I’ve got our starting point.

Just a little bit here, we’ve got some import statements. We’re using the Slim factory. We have a couple apt-get define that. So if we were to look at localhost, actually, let’s go to our ngrok URL just because drive that point home. If we were to navigate to the base here, just try Hello, world. We click that Hello, world, right? We could tweak this over here.

Maybe– where did I go? Hello. And let’s add a emoji. Save that. And now, if I were to refresh and say Hello, world, so we are editing this live and proof that we’re running this guy. All right. Close this out.

OK. So the first thing we need to do is go ahead and just grab our environment variables. And I’m going to define the Bandwidth client as a global scope, just to demo purposes in a production-level application that I could see absolutely reasons you would not want to do this. But for this, the purposes of the demo, I think it’s good enough to get the point across.

I’m going to create this new configuration object. It does take an array with the basic auth user name, tying to the Bandwidth token. The messaging basic off password is your messaging secret. And then for voice, it’s a little bit more straightforward, where your basic auth username is that Bandwidth API user that we talked about earlier. And your API password is just that is your voice basic auth password.

We can then pass that configuration object into the Bandwidth client mentor or generator and get back a new client. So we’re not going to be doing anything with the client yet, but we will in a minute. So first thing we need to do is we’re going to dive into the messaging part of it, and we want to reply to an inbound message with a Hello from Bandwidth. Or if they text us the word dog, we obviously know that they want a dog picture. So we’re going to oblige.

So we need to go ahead and parse out that messaging callback. We need to look at it, see what the text says. And we also need to check and see if it’s a DLR, a DLR being a delivery receipt. So with every text message that Bandwidth sends, we always send a callback back to your server as specified in the application ID, letting you know the status of that message once it gets to a terminal state.

So for MMS, we can only tell in the downstream carrier has accepted that message. But for a traditional text message, if you’re using toll-free, we can get a little bit further detail but we still always need to accept that call back, so we’re going to do a little bit there. OK. A lot to say, it’s going to copy and paste here as we go. So we can take a look here what we’ve done.

We are defining our globals because we know we’re going to need that client that we defined up here. We’re going to need our account ID, and we’re going need our messaging application ID. The Slim framework provides this nice little request object that lets us grab the content.

It does have a function in here, request get parse body, which is really great for most instances. However, it does deserializer the raw content into a native structure, which are custom deserializer don’t like. They like a string. And so for using this Slim framework, we just need the raw JSON string.

And then, we’re going to pass that into our API helper to deserialize that. I’ve got my data. It does take a nice class in here. So if you provide a class, it will generate the actual methods there for you or the actual class that gives you the nice methods on top of it. And specifically, for our inbound messages, if we head to our documentation, and we look at the callbacks for– we’ll looking at a group message– we can see that every inbound event coming from the messaging API to your server is going to come as an array or a list.

And there’s only ever going to be one object in that, but it does still come as an array. So we do need to tell our deserializer that this is an array of objects. So that will spit back this messaging callbacks, and then I’m just going to pop the first one off and go ahead and tell our IDE that this is going to be a Bandwidth callback message, so we get the nice call back.

We get the nice access methods there, instead of having to refer to them as a dictionary key value pair. They’re intelligent. Therefore, she’s all the features of the IDE. And so pop that off, tell our IDE that this is going to be a Bandwidth callback message.

And then I’m going to grab this internal message object. So you can kind of see here we have this metadata, like type 2 scription. And then our message has got all the good content in there, like the text, who’s it from, who was it to. In this case, it’s a group message. It was sent to multiple people, and it’s really got a lot of stuff that we want to work with. So I just put this in here as a way to show how to access that.

Let’s go ahead and go back to our little tutorial. So let’s check and see if it’s a DLR. So the way that I personally do this, I check the inter message body right here. I checked the direction of that, and then I’m just going to dunk it to lower case, trim off any messages, just for string safety, and compare that to ALT.

So if it was indeed a DLR, I’m just going to return 200 back to the band with API. This is where you would want to log something to a database, update a DB record, or keep track of that message history. But in our case, again, we’re just going to ignore it really. Just reply back to the Bandwidth server, letting you know, hey, we received the callback.

If we don’t reply back or reply back with a 500 or something like that, Bandwidth is going to keep trying to send that callback over the next 24 hours. We do back off exponentially. So the first one may come at second after we get no response. The next one may be a minute than five minutes, et cetera. So we do like for you to return a response. If we don’t get a response within the first little bit. The first time out we do try again, after 24 hours, we give up. So I don’t want to play nice with the Bandwidth ecosystem.

The next thing I want to do is to reply to the group. So if we saw over here, we have a group message. So I want to go ahead and future proof my little app here so that if I’m in a group text message and someone reply or sends a message “dog” to the group, I still reply to every single person in that group and not just the person who texted me.

So if we look at our incoming group message for example, the two is going to be an array of values. And you see that the owner, this is going to be the Bandwidth number. It’s also the same one up here. But this right here, this owner is the Bandwidth phone number. This is the Bandwidth number that received the example, that received the text. And this is who we want to reply from.

However, you’ll notice that it is also in the two field here. So what I’m going to do is I’m going to restrict. I’m going to find this owner in the two array, remove it, and then I’m going to add whoever sent it to this. So in this case, I would take out this 902 and add in this 901 to this example.

So let’s go ahead and grab that code and building the two array. Grab this. So we got here. This is just some of my unfamiliarity with PHP. This array object is going to require us to get a copy of that array to have a native array instead of this abstract object called an array object. Then I’m going to search that, needle in a haystack, grab the index of that, remove the index, and then push in that from value that we have there.

So at this point, I’ve got a pretty good idea of what my message request should be. I know who to send it from. I know who to send it to, what array. I know what the application ID should be. And I can go ahead and build that.

So I’m going to go ahead and grab this right here and paste this in. And so I’m going to call this– I’m going to grab the message request object and call it message request. Go ahead and add the values right owner to the from. We do need to make sure that we tell the SDK to grab the array values of the two number, not the key values. So if you don’t do this right here, it will create a malformed object to send to the API.

We only need the values. We don’t need indexes of the app. Go ahead and set that application ID. So at this point, we’ve got a pretty solid message request. We need to check if it’s a dog. So let me grab a one little thing. I forgot to add there. Take a note.

Right here, so we’ve got this little Boolean called is dog, right? If the message text is dog, it’s going to set that to true. We’re going to check if it’s true. If it is a dog, then– now something is fussing at me. Undefined variable message text, oh, that’s right. I forgot to pull that out.

Same thing as I did before, I want to– no. This one’s undefined variable message request that I do this out of order. I did. Let’s try that again. There we are.

OK, apologies about that. So now, we have our message text. I, again, take it to lower case. I trim off any trailing or proceeding white spaces, and then I do my comparison so that way, if it was the dog, we’re going to get the dog. I’m going to set that text to the emoji, and I’m going to set the media field to this picture that I already have loaded in here. It’s going to be of a dog. And otherwise, the text is just going to be Hello from Bandwidth.

So at this point, we’ve got our entire message request. We’re ready to actually send that back to the Bandwidth server. So I want to go ahead and grab my messaging client.

Grabbed my messaging client here using the GET client method off of our GET messaging method there. It does generate what we call an API response when we create that message, just to give you an idea of what is available off of the API response. You have the headers, the status code, and the actual result, the result being the body of that message request.

So I do want to grab that using type hints here to make our IDE really get a little bit better feel for what’s there for us, message response. I can see the media text application ID. All that good stuff is all ready in there for me. I do go ahead and pull out the message ID here. I don’t have any way to print that to console, but it’s there, so we can inspect it in a minute.

So if I go ahead and save this and pull up Dialpad here, if I were to– make sure this is the right phone number. Here we are. So if I were to send “Hi,” we’ll see the inbound message coming to Bandwidth there, and there is my Hello from Dallas.

If I send the word dog, survey says there we are. There’s our dog picture. Now, just to show a little bit of what’s going on behind the scenes, I am going to head to our request here. I’m just going to copy this, head up, and fire up Postman, and going to paste this in here. So this is going to simulate me hitting this URI from Bandwidth, but I’m actually going to put in a break point right here.

Oh. Curious if this is going to work– and let’s click Send. Yeah. There we are. So now, we can see our message ID. Oh, there it goes. Our message ID is not going to be defined there. Let’s go. Here we are.

And here is our message ID, so we are logging it. We just don’t have a great way to get to it using PHP. OK. So at this point, let’s take out my break points because otherwise, we won’t respond in a friendly-enough time. You see there’s me, sending that message as we just did.

But now, let’s build our voice app. So at this point, we’ve got a message up and running. We need to build our voice application. So again, thinking through, we’re going to play a little arithmetic game. So thinking through, we do not need or need to parse the inbound caller.

We don’t care who it’s from. We don’t care who it was to. We don’t really care anything about that call except that it happened. So we’re not really going to inspect that inbound call request. We’re going to reply with the same BXML that we would have every single time, every time.

So I’m not actually going to parse anything from this request object. For the inbound call part, we will need to do this for the gather response, and we’ll talk about that in a minute. So I’m going to use our SDKs case. You can get an idea of what the SDK methods or verbs are going to look like right here.

Play audio. If you go to PHP, it shows you how to actually generate that using the SDKs. So I did talk about Gather as the one thing I want to do. So Gather does require a next step. So based on whatever the input was or lack of input if it times out, we want to make a decision. We want to do– want to make a next step.

And so we do need a Gather URL to route that event to another point in our code. We also want to go ahead and nest the request to speak it or to press a digit or a request for our arithmetic in there. And so for this regards, we want to nest within the Gather because if we don’t, we have to wait until that entire sentence is done being spoke before we can collect input.

So if you have a very long prompt that you’ve probably heard, just like please say, please wait until the end of this sentence before making your selection, you can usually go ahead and push 5 or whatever real quick. And it’ll stop the audio playing and move on. That’s the idea. So that’s why you want to get, nest it, your speaks in it, or you play audio within the Gather verb.

If we head back over here, I’m just going to grab this whole thing and speak to it and duplicate. There we are. OK. So I do have my speaks in it. Verb here generated. Hello, let’s play a game. What is 9 plus 2? Very basic arithmetic, we’re not trying to do rocket science here.

I want to go ahead and create my Gather request. I do set my Gather URL to this callbacks voice Gather, so I will need to add a route to my PHP application. I’m only going to let them press two digits, so I’m not saying that you can get this super wrong. We’re going to limit you to be able to get two digits, so you can’t be that badly off. You can’t be off by an order of magnitude for our 9 plus 2 game.

I’m going to give you 10 seconds to press the first digit, and then this right here is going to be nesting that original speaks in it within that verb. And I’m just choosing Kate’s voice here. I build the response. And just because of the way that my library is written, I’m just going to call it the XML response.

I’m going to add that verb. This method right here, the XML, 2b XML, this is going to be generating the string that we’re going to send back to the Bandwidth callback or back to the Bandwidth servers as a response to the callback. I’ve done just some little upkeep here on my response. You don’t need to do this.

You do need to return a success code, but you don’t necessarily need to set the header to application XML. I’ve just done that to keep the content type to match what the actual body is. And then using the Slim framework, I can write that be XML to there.

So at this point, what will happen when I call this number, we’re going to play a game. What’s 9 plus 2? It’s going to ask me to press the digits. And if I do, it doesn’t do anything right now because I need to define this route right here. So let’s go ahead and grab that.

Don’t descend right here. And so at this point, I now have an endpoint to start inspecting that request. So unfortunately, we do not have the nice models built for the voice callbacks quite yet. We’re looking to get those hopefully soon, so we do need to extract the digits from using the real name. We don’t get the nice IDE hint in this right here, but it does allow us to get the parsed body, which site would access the data there with the Slim framework.

OK. Also happened to have just two weigh files already generated and saved off into the internet land, one is going to be success, and the other one is going to be a fail, which is the sad trombone. Then I want to inspect those digits. And so if the digits are 11 or 1-1, then I want to play my success file. Otherwise, I want to play my fail file.

And let’s go ahead and build that BXML. Let’s start pasting this in here. So my play audio verb is going to take that audio URI there. I’m going to go ahead and generate a new response. I’m going to add that play audio verb, and then I also want to hang up that call once I’m done playing the audio.

So we could wait until the Bandwidth interpreter figures out that there’s not a next step after it gets to the end of that BXML file, but we’re Bandwidth good citizens, so I’m going to explicitly say please hang up that call once we’re done, either celebrating the success or sad tromboning your failure. And again, you don’t have to set the header to content type application XML. Just a nice thing to do, but you do need to respond with a 200 status quo.

OK. So at this point, we can demo that. Let me just head back over to Dialpad here. I’m going to go ahead and call this number. Let me rerun this. I’m in debug mode. Let’s try that again. Stop. OK. Let’s try again.

It’s going to hang up, but let’s take a look at what actually happened here. So here is my inbound call right from my Dialpad number to my server. And you see here is what I responded with.

So I said, Hello. Let’s play a game. What is 9 plus 2? You can see my Gather URL right up there. Then I did receive that call back to the Gather endpoint as specified. And as you saw, I got pressed 22. I didn’t get it right, and so it played the fail.wav file and then hung up the call. Let’s look and see what that was like. I get it correct.

And there was my success.wav file or tothat.wav file. So at this point, we do have a relatively simple application that’s built out to send all pictures really, as far as I’m concerned, and to quiz me on basic arithmetic. But it did demonstrate how to access BXML simple methods, talk about how to work with the responses that you get from the Bandwidth APIs, and how to build requests using the SDKs. So at this point, let’s open it up for any more questions.

TRACY: Cool. Thanks, Dan. I’ll go ahead and make some closing remarks, and we’ll just keep a minute open for any extra questions. But I failed to mention at the beginning of the webinar that you guys will receive a recording, so you’ll have all of the slides and Dan’s lovely voice emailed to you, guys, probably sometime tomorrow afternoon.

Yeah. Other than that, Dan, thank you for presenting. Thank you, guys, for joining us amidst everything that’s going on right now. And yeah. If you have any additional questions, even after you get the recording, feel free to reach out to either of us, and we’d be happy to help. But if no one has any questions, thanks so much.

DAN TOLBERT: I will wait just one more minute here OK OK. I believe that’s enough. Thank you all for hanging out.

TRACY: Yeah. Thanks, guys. Appreciate it, Dan.