Search code examples
phpregexpreg-match-all

preg_match_all works on snippets but not entire web page


I have a regex that works with small portions of code but when running it on the entire page it only returns the first match. Why?

My question is related to preg_match backreference to find ending tag. But now I have a different issue.

Regex that comes from the below code

/\{(.*?)_start\}.*\{\1_stop\}/s

Preg_match_all

$loopPattern = "/\\$this->tokenStartDelimiter(.*?)_start\\$this->tokenStopDelimiter.*\\$this->tokenStartDelimiter\\1_stop\\$this->tokenStopDelimiter/s";
preg_match_all($loopPattern, $this->template, $matches);
foreach ($matches[0] as $match) {
    $this->template = str_replace($match, '', $this->template);
}

Whole Page

<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <link rel="apple-touch-icon" href="apple-touch-icon.png">

    <title>Appointment-Plus</title>

    <!--<link rel="stylesheet" href="css/themes/default/jquery.mobile.theme-1.2.0.min.css"> -->
    <link rel="stylesheet" href="css/themes/apcv/APCV-Theme.min.css">
    <link rel="stylesheet" href="css/themes/default/jquery.mobile.structure-1.2.0.min.css">

    <script type="text/javascript" src="js/jquery-1.8.2.min.js"></script>
    <script type="text/javascript" src="js/jquery.mobile-1.2.0.min.js"></script>
    <script type="text/javascript" src="js/jquery.validate.min.js"></script>
    <script type="text/javascript" src="js/pages/all.js"></script>
    <link rel="stylesheet" href="css/mfe.css">
</head>

<body>
<div data-role="page" data-theme="d">
    <!-- Some icons by Yusuke Kamiyamane. -->
    <div data-id="head" data-role="header" data-position="fixed" data-theme="b" data-tap-toggle="false">
        <h1>DB Games</h1>
        <a href="#" id="headingsActionButton" class="ui-btn-right headingsActionButton hidden" data-role="button" data-inline="true" data-icon="check">Save</a>

    </div>

    <div id="mainContent" data-role="content">
    <!--    <div data-role="fieldcontain">
            <div style="width: 100%; height: 100px; position: relative; background-color: #fbfbfb; border: 1px solid #b8b8b8;">
                <img src="https://codiqa.com/static/images/v2/image.png" alt="image" style="position: absolute; top: 50%; left: 50%; margin-left: -16px; margin-top: -18px;">
            </div>
        </div> -->
        <div data-role="fieldcontain">
    <div data-role="collapsible-set" class="mfe_collapsibles" data-theme="c" data-inset="false">

        <div id="appointmentHeading" data-action-id="appointmentNext" data-action-text="Next" data-a data-role="collapsible" data-collapsed="true" data-collapsed-icon="arrow-r" data-expanded-icon="arrow-d" data-iconpos="right">
            <h3 class="collapsibleMainHeading">New Appointment</h3>
            <p>
                <script src="js/pages/newAppointment.js"></script>
<div data-role="collapsible-set" data-content-theme="c" class="appointmentMakeCollapsibleSet">
    <div data-role="collapsible" data-collapsed="false" data-collapsed-icon="arrow-r" data-expanded-icon="arrow-d">
        <h3>Appointment</h3>
        <p>
            <form id="appointmentPrimaryForm">

    <label for="c_id" class="select required" required>Location: </label>
    <select name="c_id" id="c_id" data-native-menu="true" class="select required" required>
        <option value="" data-placeholder="true">Choose One</option>

        <option value="261"
        selected>Mesa</option>
        <option value="1007"
        >Chandler</option>
    </select>



    {AppointmentStaffSelect_start}
    <label for="apptCreateEditStaff" class="select required" required>With: </label>
    <select name="apptCreateEditStaff" id="apptCreateEditStaff" data-native-menu="true" class="select required" required>
        <option value="" data-service="" data-placeholder="true">Choose One</option>

        <option data-service="{Services}" value="5490"
        >Brian</option>
        <option data-service="{Services}" value="4388"
        >Mckenna</option>
        <option data-service="{Services}" value="4387"
        >Katelyn</option>
        <option data-service="{Services}" value="783"
        >David</option>
        <option data-service="{Services}" value="4389"
        >Ryan</option>
        <option data-service="{Services}" value="4394"
        >Ryan - c</option>
        <option data-service="{Services}" value="784"
        >Tami</option>
        <option data-service="{Services}" value="4390"
        >Jake</option>
    </select>
    {AppointmentStaffSelect_stop}
    <p>{Test}</p>
{AppointmentTest_start}
<p></p>
{AppointmentTest_stop}

    <label for="service_id" class="select required">Service: </label>
    <select name="service_id" id="service_id" data-native-menu="true" class="select required" required>
        <option value="" data-placeholder="true" data-duration="0" data-cost="0.00">Choose One</option>
        <option value="1174|30 Minute Gaming Pass" class="{Classes}" data-cost="3.00" data-duration="30">30 Minute Gaming Pass</option>
        <option value="1176|1 Hour Gaming Pass" class="{Classes}" data-cost="5.00" data-duration="60">1 Hour Gaming Pass</option>
        <option value="1177|2 Hour Gaming Pass" class="{Classes}" data-cost="7.00" data-duration="120">2 Hour Gaming Pass</option>
    </select>

    {AppointmentAddonService_start}
    <fieldset data-role="controlgroup" id="apptCreateEditAddonServicesFieldset">
        <legend>Add-on Service(s):</legend>

        <input type="checkbox" name="aos-5059" id="aos-5059" data-duration="30" data-cost="10.00" class="custom {Classes}"   />
        <label for="aos-5059" class="{Classes}">Unlimited Snack Bar</label>

        <input type="checkbox" name="aos-5060" id="aos-5060" data-duration="30" data-cost="5.00" class="custom {Classes}"   />
        <label for="aos-5060" class="{Classes}">Unlimited Energy Drinks</label>

        <input type="checkbox" name="aos-5061" id="aos-5061" data-duration="30" data-cost="1.00" class="custom {Classes}"   />
        <label for="aos-5061" class="{Classes}">Candy Bar</label>

    </fieldset>
    {AppointmentAddonService_stop}

    <label for="apptCreateEditDate" class="select required" required>Date: </label>
    <select name="apptCreateEditDate" id="apptCreateEditDate" data-native-menu="true" class="select required" required>
        <option value="" data-placeholder="true">Choose One</option>
        {apptCreateEditDateOption_start}
        <option value="{Value}"
        {Selected}>{Name}</option>{apptCreateEditDateOption_stop}
    </select>

    <label for="apptCreateEditTime" class="select required" required>Time: </label>
    <select name="apptCreateEditTime" id="apptCreateEditTime" data-native-menu="true" class="select required" required>
        <option value="" data-placeholder="true">Choose One</option>
        {apptCreateEditTimeOption_start}
        <option value="{Value}"
        {Selected}>{Name}</option>{apptCreateEditTimeOption_stop}
    </select>
</form>
        </p>
    </div>
    <div data-role="collapsible" data-collapsed="true" class="ui-disabled" data-collapsed-icon="arrow-r" data-expanded-icon="arrow-d">
        <h3>Children</h3>
        <p>
            Page 2
        </p>
    </div>
    <div data-role="collapsible" data-collapsed="true" class="ui-disabled" data-collapsed-icon="arrow-r" data-expanded-icon="arrow-d">
        <h3>Appointment Details</h3>
        <p>



<label for="customer_notes">Special Instructions for Customer: </label>
<textarea cols="40" rows="8" name="customer_notes" id="customer_notes"></textarea>


{apptCreateEditPONumber_start}
<label for="apptCreateEditPONumber" class="{Class}">{LName}: </label>
<input type="text" id="apptCreateEditPONumber" name="apptCreateEditPONumber" class="{Class}" value=""/>
{apptCreateEditPONumber_stop}

{apptCreateEditVehicleMake_start}
<label for="apptCreateEditMakeId" class="select {Class}">{LName}: </label>
<select name="apptCreateEditMakeId" id="apptCreateEditMakeId" data-native-menu="true" class="select {Class}">
    <option value="" data-placeholder="true">Choose One</option>
    {apptCreateEditMakeOption_start}
    <option value="{Value}"
    {Selected}>{Name}</option>{apptCreateEditMakeOption_stop}
</select>
{apptCreateEditVehicleMake_stop}

{apptCreateEditVehicleModel_start}
<label for="apptCreateEditModelId" class="select {Class}">{LName}: </label>
<select name="apptCreateEditModelId" id="apptCreateEditModelId" data-native-menu="true" class="select {Class}">
    <option value="" data-placeholder="true">Choose One</option>
    {apptCreateEditModelOption_start}
    <option value="{Value}" class="{Make}"
    {Selected}>{Name}</option>{apptCreateEditModelOption_stop}
</select>
{apptCreateEditVehicleModel_stop}

{apptCreateEditVehicleYear_start}
<label for="apptCreateEditModelYear" class="{Class}">{LName}: </label>
<select name="apptCreateEditModelYear" id="apptCreateEditModelYear" data-native-menu="true" class="select {Class}">
    <option value="" data-placeholder="true">Choose One</option>
    {apptCreateEditYearOption_start}
    <option value="{Value}"
    {Selected}>{Name}</option>{apptCreateEditYearOption_stop}
</select>
{apptCreateEditVehicleYear_stop}

{apptCreateEditVehicleOther_start}
<label for="apptCreateEditOtherVehicle" class="{Class}">{LName}: </label>
<input type="text" id="apptCreateEditOtherVehicle" name="apptCreateEditOtherVehicle" class="{Class}" value="{ApptCreateEditVehicleOther}"/>
{apptCreateEditVehicleOther_stop}

{apptCreateEditVehicleVIN_start}
<label for="apptCreateEditVIN" class="{Class}">{LName}: </label>
<input type="text" id="apptCreateEditVIN" name="apptCreateEditVIN" class="{Class}" value="{ApptCreateEditVehicleVIN}"/>
{apptCreateEditVehicleVIN_stop}

{apptCreateEditVehicleOdometer_start}
<label for="apptCreateEditOdometer" class="{Class}">{LName}: </label>
<input type="number" id="apptCreateEditOdometer" name="apptCreateEditOdometer" class="{Class}" value="{ApptCreateEditVehicleOdometer}"/>
{apptCreateEditVehicleOdometer_stop}
        </p>
    </div>

    <div data-role="collapsible" class="postRegistrationCollapsible ui-disabled" data-collapsed="true" data-collapsed-icon="arrow-r" data-expanded-icon="arrow-d">
        <h3>Register / Login</h3>
        <p>
            <script src="js/pages/postRegistration.js"></script>
<div data-role="fieldcontain" class="hidden">
    <p id="postRegistrationMessage"></p>
</div>
<a href="#registerPopupDiv" class="postRegistrationButtons" data-role="button" data-rel="popup" data-inline="true">Register</a>
<a href="#loginPopupDiv" class="postRegistrationButtons" data-role="button" data-rel="popup" data-inline="true">Login</a>
<div id="registerPopupDiv" data-role="popup" data-overlay-theme="a" class="ui-content" data-corners="true" data-shadow="true" data-position-to="window" data-transition="flow" data-theme="c">
    <form id="registerFormPopup" data-ajax="false">
    <div data-role="fieldcontain" data-inset="false">

<label for="first_name" class="required">First Name1:</label>
<input type="text" id="first_name" name="first_name" value="{Value}" class="required" required/>

<label for="middle_name" class="{Class}">Middle Name:</label>
<input type="text" id="middle_name" name="middle_name" value="{Value}" class="{Class}" {Required}/>

<label for="last_name" class="required">Last Name1:</label>
<input type="text" id="last_name" name="last_name" value="{Value}" class="required" required/>

<label for="email" class="{Class}">eMail:</label>
<input type="email" id="email" name="email" value="{Value}" class="{Class}" {Required}/>

<label for="login" class="{Class}">Login:</label>
<input type="text" id="login" name="login" value="{Value}" class="{Class}" {Required}/>

<label for="password" class="{Class}">Password:</label>
<input type="password" id="password" name="password" value="{Value}" class="{Class}" {Required}/>

<label for="customer_id_unique">Customer ID:</label>
<input type="text" id="customer_id_unique" name="customer_id_unique" value="{Value}" class="readonly" readonly/>

    </div>
    <div data-role="fieldcontain" data-inset="false">
        <input type="button" name="registerPopupButton" id="registerPopupButton" value="Register"/>
    </div>
</form>
</div>
<div id="loginPopupDiv" data-overlay-theme="a" data-role="popup" class="ui-content" data-corners="true" data-shadow="true" data-position-to="window" data-transition="flow" data-theme="c">
    <form id="loginFormPopup" data-ajax="false">
    <div data-role="fieldcontain" class="ui-hide-label">
        <label for="login-username">Username:</label>
        <input type="text" name="login-username" id="login-username" value="" placeholder="Username"/>

    </div>
    <div data-role="fieldcontain" class="ui-hide-label">

        <label for="login-password">Password</label>
        <input type="password" name="login-password" id="login-password" value="" placeholder="Password"/>
    </div>
    <div data-role="fieldcontain" data-inset="false">
        <input type="button" name="loginPopupButton" id="loginPopupButton" value="Login"/>
    </div>
    <div data-role="fieldcontain" data-inset="false">
        <a href="#" id="forgotPopupPassword" data-rel="popup">Forgot Password?</a>
    </div>
</form>
</div>
        </p>
    </div>

    <div data-role="collapsible" data-collapsed="true" class="ui-disabled" data-collapsed-icon="arrow-r" data-expanded-icon="arrow-d">
        <h3>Verification</h3>
        <p>
            Page 5
        </p>
    </div>
</div>
            </p>
        </div>

        {RegisterSection_start}
        <div id="registerHeading" class="preRegistration" data-action-id="register" data-action-text="Register" data-role="collapsible" data-collapsed="true" data-collapsed-icon="arrow-r" data-expanded-icon="arrow-d" data-iconpos="right">
            <h3 class="collapsibleMainHeading">Register</h3>
            <p>
                <script src="js/pages/register.js"></script>
<form id="registerForm" data-ajax="false">
    <div data-role="fieldcontain" data-inset="false">

<label for="first_name" class="required">First Name1:</label>
<input type="text" id="first_name" name="first_name" value="{Value}" class="required" required/>

<label for="middle_name" class="{Class}">Middle Name:</label>
<input type="text" id="middle_name" name="middle_name" value="{Value}" class="{Class}" {Required}/>

<label for="last_name" class="required">Last Name1:</label>
<input type="text" id="last_name" name="last_name" value="{Value}" class="required" required/>

<label for="email" class="{Class}">eMail:</label>
<input type="email" id="email" name="email" value="{Value}" class="{Class}" {Required}/>

<label for="login" class="{Class}">Login:</label>
<input type="text" id="login" name="login" value="{Value}" class="{Class}" {Required}/>

<label for="password" class="{Class}">Password:</label>
<input type="password" id="password" name="password" value="{Value}" class="{Class}" {Required}/>

<label for="customer_id_unique">Customer ID:</label>
<input type="text" id="customer_id_unique" name="customer_id_unique" value="{Value}" class="readonly" readonly/>

    </div>
    <div data-role="fieldcontain" data-inset="false">
        <input type="button" name="register" id="register" value="Register"/>
    </div>
</form>
            </p>
        </div>
        {RegisterSection_stop}
        <div data-role="collapsible" class="preRegistration" data-collapsed="true" data-collapsed-icon="arrow-r" data-expanded-icon="arrow-d" data-iconpos="right">
            <h3 class="collapsibleMainHeading">Login</h3>
            <p>
                <script src="js/pages/login.js"></script>
<form id="loginForm" data-ajax="false">
    <div data-role="fieldcontain" class="ui-hide-label">
        <label for="login-username">Username:</label>
        <input type="text" name="login-username" id="login-username" value="" placeholder="Username"/>

    </div>
    <div data-role="fieldcontain" class="ui-hide-label">

        <label for="login-password">Password</label>
        <input type="password" name="login-password" id="login-password" value="" placeholder="Password"/>
    </div>
    <div data-role="fieldcontain" data-inset="false">
        <input type="submit" name="loginSubmit" id="loginSubmit" value="Login"/>
    </div>
    <div data-role="fieldcontain" data-inset="false">
        <a href="#passwordResetDiv" data-rel="popup">Forgot Password?</a>
    </div>
</form>
<script src="js/pages/passwordReset.js"></script>

<div id="passwordResetDiv" data-overlay-theme="a" data-role="popup" class="ui-content" data-corners="true" data-shadow="true" data-position-to="window" data-transition="flow" data-theme="c">
    <form id="passwordResetForm">
        <div data-role="fieldcontain" class="ui-hide-label">
            <label for="email">Email:</label>
            <input type="text" name="email" id="email" value="" placeholder="Email" class="required" required/>
        </div>
        <div data-role="fieldcontain" data-inset="false">
            <input type="submit" name="submit" id="submit" value="Reset Password"/>
        </div>
    </form>
</div>

            </p>
        </div>

    </div>
</div>
    </div>

</div>
</body>
</html>

Result from entire page

Array
(
    [0] => Array
        (
            [0] => {AppointmentStaffSelect_start}
    <label for="apptCreateEditStaff" class="select required" required>With: </label>
    <select name="apptCreateEditStaff" id="apptCreateEditStaff" data-native-menu="true" class="select required" required>
        <option value="" data-service="" data-placeholder="true">Choose One</option>

        <option data-service="{Services}" value="5490"
        >Brian</option>
        <option data-service="{Services}" value="4388"
        >Mckenna</option>
        <option data-service="{Services}" value="4387"
        >Katelyn</option>
        <option data-service="{Services}" value="783"
        >David</option>
        <option data-service="{Services}" value="4389"
        >Ryan</option>
        <option data-service="{Services}" value="4394"
        >Ryan - c</option>
        <option data-service="{Services}" value="784"
        >Tami</option>
        <option data-service="{Services}" value="4390"
        >Jake</option>
    </select>
    {AppointmentStaffSelect_stop}
        )

    [1] => Array
        (
            [0] => AppointmentStaffSelect
        )

)

Small portions that are processed fine

{AppointmentStaffSelect_start}
    <label for="apptCreateEditStaff" class="select required" required>With: </label>
    <select name="apptCreateEditStaff" id="apptCreateEditStaff" data-native-menu="true" class="select required" required>
        <option value="" data-service="" data-placeholder="true">Choose One</option>

        <option data-service="{Services}" value="5490"
        >Brian</option>
        <option data-service="{Services}" value="4388"
        >Mckenna</option>
        <option data-service="{Services}" value="4387"
        >Katelyn</option>
        <option data-service="{Services}" value="783"
        >David</option>
        <option data-service="{Services}" value="4389"
        >Ryan</option>
        <option data-service="{Services}" value="4394"
        >Ryan - c</option>
        <option data-service="{Services}" value="784"
        >Tami</option>
        <option data-service="{Services}" value="4390"
        >Jake</option>
    </select>
    {AppointmentStaffSelect_stop}
    <p>{Test}</p>
{AppointmentTest_start}
<p></p>
{AppointmentTest_stop}

    <label for="service_id" class="select required">Service: </label>
    <select name="service_id" id="service_id" data-native-menu="true" class="select required" required>
        <option value="" data-placeholder="true" data-duration="0" data-cost="0.00">Choose One</option>
        <option value="1174|30 Minute Gaming Pass" class="{Classes}" data-cost="3.00" data-duration="30">30 Minute Gaming Pass</option>
        <option value="1176|1 Hour Gaming Pass" class="{Classes}" data-cost="5.00" data-duration="60">1 Hour Gaming Pass</option>
        <option value="1177|2 Hour Gaming Pass" class="{Classes}" data-cost="7.00" data-duration="120">2 Hour Gaming Pass</option>
    </select>

    {AppointmentAddonService_start}
    <fieldset data-role="controlgroup" id="apptCreateEditAddonServicesFieldset">
        <legend>Add-on Service(s):</legend>

        <input type="checkbox" name="aos-5059" id="aos-5059" data-duration="30" data-cost="10.00" class="custom {Classes}"   />
        <label for="aos-5059" class="{Classes}">Unlimited Snack Bar</label>

        <input type="checkbox" name="aos-5060" id="aos-5060" data-duration="30" data-cost="5.00" class="custom {Classes}"   />
        <label for="aos-5060" class="{Classes}">Unlimited Energy Drinks</label>

        <input type="checkbox" name="aos-5061" id="aos-5061" data-duration="30" data-cost="1.00" class="custom {Classes}"   />
        <label for="aos-5061" class="{Classes}">Candy Bar</label>

    </fieldset>
    {AppointmentAddonService_stop}

Result from small portion page

Array
(
    [0] => Array
        (
            [0] => {AppointmentStaffSelect_start}
    <label for="apptCreateEditStaff" class="select required" required>With: </label>
    <select name="apptCreateEditStaff" id="apptCreateEditStaff" data-native-menu="true" class="select required" required>
        <option value="" data-service="" data-placeholder="true">Choose One</option>

        <option data-service="{Services}" value="5490"
        >Brian</option>
        <option data-service="{Services}" value="4388"
        >Mckenna</option>
        <option data-service="{Services}" value="4387"
        >Katelyn</option>
        <option data-service="{Services}" value="783"
        >David</option>
        <option data-service="{Services}" value="4389"
        >Ryan</option>
        <option data-service="{Services}" value="4394"
        >Ryan - c</option>
        <option data-service="{Services}" value="784"
        >Tami</option>
        <option data-service="{Services}" value="4390"
        >Jake</option>
    </select>
    {AppointmentStaffSelect_stop}
            [1] => {AppointmentTest_start}
<p></p>
{AppointmentTest_stop}
            [2] => {AppointmentAddonService_start}
    <fieldset data-role="controlgroup" id="apptCreateEditAddonServicesFieldset">
        <legend>Add-on Service(s):</legend>

        <input type="checkbox" name="aos-5059" id="aos-5059" data-duration="30" data-cost="10.00" class="custom {Classes}"   />
        <label for="aos-5059" class="{Classes}">Unlimited Snack Bar</label>

        <input type="checkbox" name="aos-5060" id="aos-5060" data-duration="30" data-cost="5.00" class="custom {Classes}"   />
        <label for="aos-5060" class="{Classes}">Unlimited Energy Drinks</label>

        <input type="checkbox" name="aos-5061" id="aos-5061" data-duration="30" data-cost="1.00" class="custom {Classes}"   />
        <label for="aos-5061" class="{Classes}">Candy Bar</label>

    </fieldset>
    {AppointmentAddonService_stop}
        )

    [1] => Array
        (
            [0] => AppointmentStaffSelect
            [1] => AppointmentTest
            [2] => AppointmentAddonService
        )

)

How do I get it to parse the entire page and return all the matches? The small portions are parsing fine but when I try and search the entire page, it only seems to return the results before the first {Test} token.


Solution

  • The first thing you should change is making the second repetition ungreedy, too:

    /\{(.*?)_start\}.*?\{\1_stop\}/s
    

    Right now this is not much of an issue, because you never have the same tag twice. But if you had, you would match from the first opening to the last closing tag. This should also (in most cases) slightly increase performance.

    Now I can only assume that the reason you why only get the first match is, that the regex engine just takes way too long backtracking through both of your repetitions.

    In case of {Test} this is roughly what happens: for every step of the backtracking (.*?) will consume one more character. So first T, then Te, then Tes, then Test, then Test} and so on. If any of those matches is followed by an underscore, it would continue after the repetition. Now you can already see, that . matches any character (including line breaks, because of the s modifier). So this thing will try once for every single character in the rest of you string (although, you know you only want to look until the next closing }. But then it gets even worse. Once the (.*?) hast consumed everything from Test}... until {AppointmentTest, it is actually able to match _start}. Now it will assume that is has found a match, with the tag name being Test}</p>\n{AppointmentTest and will do the same backtracking through the rest of your file (one character at a time), never finding the closing tag for this. This is especially costly, because the engine has to access the captured group every time it encounters a {. And after it is done, it will continue with the backtracking of the first, repetition until it thinks it has found another tag, starting with Test}... and ending with ...{AppointmentAddonService. And this happens for every other opening tag you have in your code. I can imagine this will easily cause the regex engine to abort the search. You could check with preg_last_error whether that is actually the case.

    Now how to avoid this. Have the engine stop the backtracking earlier. That is, allow fewer possibilities for your repetition. For example, you know that your tags will never contain a } because, that is what delimits their end. So if you simply say, the tag name can contain any character except for } the engine will at most continue until the end of the tag (instead of through the whole file), and at the same time can never match a tag from one opening { to an unrelated _start}. You do this using a negated character class. This also means you can remove the ungreedy-? from the first repetition (because it can never pass beyond the end of the opening tag anyway):

    /\{([^}]*)_start\}.*?\{\1_stop\}/s
    

    The second repetition will still backtrack through the whole file, but that is not really avoidable, because after all your whole file might be wrapped in two of your tags. However, this backtracking is only executed once at most. And never for tags like {Test} that do not end in _start.

    Also note, that if you disallow underscores in your tag names (so there will never be something like {Custom_section_start}) you can also include the underscore in the negated character class, but that is a rather minor optimization.

    In any case, I hope this solves your issue (if not, please let me know what preg_last_error returns).

    What can you take away from this? Be very careful before using .* - especially if it is supposed to match some delimited content. You can do some reading here or generally through the tutorial on that website. It is very well written and tells you what is going on under the hood of the regex engine.